2016-12-08 1 views
1

Une table MySQL contient les deux tables de tableau suivant (simplifié):utilisant plage avec clé composite

(~13000)   (~7000000 rows)  
---------------  -------------------- 
| packages |  | packages_prices | 
---------------  -------------------- 
| id (int) |<- ->| package_id (int) | 
| state (int) |  | variant_id (int) | 
- - - - - - -  | for_date (date) | 
        | price (float) | 
        - - - - - - - - - 

Chaque package_id/for_date combinaison a seulement quelques (moyenne 3) variantes. Et state est 0 (inactif) ou 1 (actif). Environ 4000 des 13000 sont actifs.

D'abord, je veux juste savoir quels paquets ont un ensemble de prix (quelle que soit la variation), donc j'ajouter une clé composite couvrant (1) for_date et (2) pid et je recherche:

select distinct package_id from packages_prices where for_date > date(now()) 

Cette La requête prend 1 secondes pour renvoyer 3500 lignes, ce qui est trop. Un Explain m'indique que la clé composite est utilisée avec key_len 3, et 2000000 lignes sont examinées avec 100% filtré avec la plage de types. Using where; Using index; Using temporary. Le distinct le ramène à 3500 lignes.

Si je supprime distinct, le Using temporary n'est plus mentionné, mais la requête renvoie alors 1000000 lignes et prend encore 1 secondes.

question 1: pourquoi cette requête est-elle si lente et comment puis-je accélérer sans avoir à ajouter ou modifier les colonnes de la table? Je m'attends à ce que, compte tenu de la clé composite, cette requête devrait être en mesure de coûter moins de 0,01s.

Maintenant, je veux savoir quels actifs paquets qui ont un ensemble de prix.

Donc j'ajoute une clé sur state et j'ajoute une nouvelle clé composite comme ci-dessus, mais dans l'ordre inverse. Et j'écris ma requête comme ceci:

select distinct packages.id from packages 
inner join packages_prices on id = package_id and for_date > date(now()) 
where state = 1 

La requête prend maintenant 2 secondes. Une explication me dit pour la table packages la clé sur state est utilisée avec key_len 4, examine 4000 lignes et filtre 100% type type ref. Using index; Using temporary. Et pour la table packages_prices, la nouvelle clé composite est utilisée avec key_len 4, examine 1000 lignes et filtre 33.33% avec le type ref. Using where; Using index; Distinct. Le distinct le ramène à 3000 lignes.

Si je supprime distinct, les Using temporary et Distinct ne sont plus mentionnés, mais la requête renvoie 850000 lignes et prend 3 secondes.

question 2: Pourquoi la requête est-elle beaucoup plus lente maintenant? Pourquoi la plage n'est plus utilisée selon l'Explain? Et pourquoi le filtrage avec la nouvelle clé composite a-t-il chuté à 33,33%? Je m'attendais à ce que la clé composite filtre de nouveau 100% procent.

Tout cela semble très basique et trivial, mais cela m'a coûté des heures et des heures et je ne comprends toujours pas ce qui se passe vraiment sous le capot.

+0

D'abord vous devriez probablement faire deux questions séparées. Alors s'il vous plaît inclure créer une table, créer un index, et nous montrer le plan d'explication complet. –

+0

quand vous avez dit 'variants' vous voulez dire différent' packages_prices'? –

+0

@HoneyBadger Ce sont deux tables séparées. Mais il l'a mis côte à côte. –

Répondre

1

Vos observations sont cohérentes avec le fonctionnement de MySQL. Pour votre première requête, en utilisant l'index (for_date, package_id), MySQL démarrera à la date spécifiée (en utilisant l'index pour trouver cette position), mais devra ensuite aller à la fin de l'index, car chaque entrée suivante peut révéler une inconnue package_id. Un package_id spécifique pourrait, par exemple, viennent d'être utilisés sur les dernières for_date. Cette recherche s'ajoutera à vos 2000000 lignes examinées.Les données pertinentes sont extraites de l'index, mais cela prendra encore du temps.

Que faire à ce sujet?

Avec une ré-écriture créative, vous pouvez transformer votre requête au code suivant:

select package_id from packages_prices 
group by package_id 
having max(for_date) > date(now()); 

Il vous donnera le même résultat que votre première requête: s'il y a au moins un for_date > date(now()) (ce qui en fera partie de votre resultset), ce sera aussi vrai pour max(for_date). Mais ceci n'aura qu'à cocher une ligne par package_id (celle ayant max(for_date)), toutes les autres lignes avec for_date > date(now()) peuvent être sautées. MySQL le fera par using index for group-by -optimization (ce texte devrait être affiché dans votre explain). Il faudra l'index (package_id, for_date) (que vous avez déjà) et seulement doit examiner 13000 lignes: Depuis la liste est ordonnée, MySQL peut sauter directement à la dernière entrée pour chaque package_id, qui aura la valeur pour max(for_date); puis continuez avec le prochain package_id.

En fait, MySQL peut utiliser cette méthode pour optimiser un distinct à (et le fera probablement si vous supprimez la condition sur for_date), mais n'est pas toujours capable de trouver un chemin; un optimiseur vraiment intelligent aurait pu réécrire votre requête de la même manière que moi, mais nous n'y sommes pas encore. Et en fonction de votre distribution de données, cette méthode aurait pu être une mauvaise idée: si vous avez par exemple. 7000000 package_id, mais seulement 20 d'entre eux à l'avenir, en vérifiant chaque package_id pour le maximum for_date sera beaucoup plus lent que de simplement vérifier 20 lignes que vous pouvez facilement trouver par l'index sur for_date. Donc, la connaissance de vos données jouera un rôle important dans le choix d'une meilleure stratégie (et peut-être optimale).

Vous pouvez réécrire votre deuxième requête de la même manière. Malheureusement, de telles optimisations ne sont pas toujours faciles à trouver et souvent spécifiques à une requête et à une situation spécifiques. Si vous avez une distribution différente (comme mentionné ci-dessus) ou si vous changez légèrement votre requête et ajoutez une date de fin, cette méthode ne fonctionnera plus et vous devrez trouver une autre idée.