57 votes

L'ordre des champs dans une clause WHERE affecte-t-il les performances de MySQL ?

J'ai deux champs indexés dans une table. type y userid (indices individuels, pas un composite).

type sont très limitées (disons que c'est seulement 0 ou 1), donc 50% des enregistrements de la table ont la même valeur. type . userid d'autre part, proviennent d'un ensemble beaucoup plus large, de sorte que la quantité d'enregistrements avec la même userid est petit.

L'une de ces requêtes sera-t-elle plus rapide que l'autre ?

select * from table where type=1 and userid=5
select * from table where userid=5 and type=1

De même, si les deux champs n'étaient pas indexés, cela changerait-il le comportement ?

73voto

Jim Ferrans Points 13673

SQL a été conçu pour être un langage déclaratif, et non procédural. L'optimiseur de requêtes doit donc no tenir compte de l'ordre des prédicats de la clause where pour déterminer comment les appliquer.

Je vais probablement simplifier à l'extrême la discussion suivante sur l'optimiseur de requêtes SQL. J'en ai écrit un il y a des années, en suivant ces lignes (c'était très amusant !). Si vous voulez vraiment vous plonger dans l'optimisation moderne des requêtes, consultez l'article de Dan Tow intitulé Tuning SQL de O'Reilly.

Dans un optimiseur de requêtes SQL simple, l'instruction SQL est d'abord compilée en un arbre de algèbre relationnelle opérations. Ces opérations prennent chacune un ou plusieurs tableaux en entrée et produisent un autre tableau en sortie. Scanner est un balayage séquentiel qui lit une table dans la base de données. Trier produit un tableau trié. Sélectionnez produit un tableau dont les lignes sont sélectionnées dans un autre tableau en fonction d'une condition de sélection. Projet produit un tableau avec seulement certaines colonnes d'un autre tableau. Produit croisé prend deux tableaux et produit un tableau de sortie composé de toutes les paires imaginables de leurs lignes.

De manière confuse, la clause SQL SELECT est compilée en une algèbre relationnelle Projet tandis que la clause WHERE se transforme en une algèbre relationnelle. Sélectionnez . La clause FROM se transforme en une ou plusieurs Rejoint chacun prenant deux tables en entrée et produisant une table en sortie. Il existe d'autres opérations d'algèbre relationnelle impliquant l'union, l'intersection, la différence et l'appartenance à un ensemble, mais restons simples.

Cet arbre a vraiment besoin d'être optimisé. Par exemple, si vous avez :

select E.name, D.name 
from Employee E, Department D 
where E.id = 123456 and E.dept_id = D.dept_id

avec 5 000 employés répartis dans 500 départements, l'exécution d'un arbre non optimisé produira aveuglément toutes les combinaisons possibles d'un employé et d'un département (a Produit croisé ) et ensuite Sélectionnez pour trouver la seule combinaison nécessaire. Le site Scanner d'Employé produira une table de 5 000 enregistrements, la Scanner du département produira un tableau de 500 enregistrements, le Produit croisé de ces deux tables produira une table de 2.500.000 enregistrements, et la Sélectionnez sur E.id prendra cette table de 2 500 000 enregistrements et les rejettera tous sauf un, celui qui était recherché.

(Les vrais processeurs de requêtes essaieront bien sûr de ne pas matérialiser toutes ces tables intermédiaires en mémoire).

L'optimiseur de requêtes parcourt donc l'arbre et applique diverses optimisations. L'une d'entre elles consiste à décomposer chaque Sélectionnez en une chaîne de Sélectionne un pour chacun des éléments originaux Sélectionnez Les conditions de niveau supérieur de l'utilisateur, celles qui sont liées entre elles. (C'est ce qu'on appelle la "forme normale conjonctive".) Ensuite, les petites conditions individuelles Sélectionne sont déplacées dans l'arbre et fusionnées avec d'autres opérations d'algèbre relationnelle pour en former de plus efficaces.

Dans l'exemple ci-dessus, l'optimiseur pousse d'abord l'élément Sélectionnez sur E.id = 123456 en bas de la coûteuse Produit croisé fonctionnement. Cela signifie que le Produit croisé produit simplement 500 lignes (une pour chaque combinaison de cet employé et d'un département). Ensuite, le niveau supérieur Sélectionnez for E.dept_id = D.dept_id filtre les 499 lignes non désirées. Pas mal.

S'il existe un index sur le champ id de l'employé, l'optimiseur peut combiner la fonction Scanner de l'employé avec le Sélectionnez sur E.id = 123456 pour former un index rapide Consulter le site . Cela signifie qu'une seule ligne d'employé est lue en mémoire depuis le disque au lieu de 5 000. Les choses s'améliorent.

La dernière optimisation majeure consiste à prendre le Sélectionnez sur E.dept_id = D.dept_id et de le combiner avec la Produit croisé . Cela la transforme en une algèbre relationnelle. Equijoin fonctionnement. Cela ne fait pas grand-chose en soi. Mais s'il existe un index sur Department.dept_id, alors le niveau inférieur séquentiel Scanner de département alimentant le Equijoin peut être transformé en un index très rapide Consulter le site du dossier du département de notre seul employé.

Des optimisations moins importantes consistent à pousser Projet les opérations en cours. Si le niveau supérieur de votre requête ne nécessite que E.name et D.name, et que les conditions nécessitent E.id, E.dept_id et D.dept_id, alors la fonction Scanner Les opérations n'ont pas besoin de construire des tables intermédiaires avec toutes les autres colonnes, ce qui permet de gagner de l'espace pendant l'exécution de la requête. Nous avons transformé une requête horriblement lente en deux consultations d'index et pas grand-chose d'autre.

Pour en revenir à la question initiale, disons que vous avez.. :

select E.name 
from Employee E 
where E.age > 21 and E.state = 'Delaware'

L'arbre d'algèbre relationnelle non optimisé, une fois exécuté, balayera les 5 000 employés et produira, par exemple, les 126 employés du Delaware qui ont plus de 21 ans. L'optimiseur de requêtes a également une idée approximative des valeurs de la base de données. Il sait peut-être que la colonne E.state contient les 14 états dans lesquels l'entreprise est implantée, et a une idée de la distribution de E.age. Il commence donc par vérifier si l'un des deux champs est indexé. Si E.state l'est, il est logique d'utiliser cet index pour sélectionner le petit nombre d'employés que le processeur de requêtes soupçonne d'être dans le Delaware sur la base des dernières statistiques calculées. Si seul E.age l'est, le processeur de requêtes décide probablement que cela ne vaut pas la peine, puisque 96% de tous les employés ont 22 ans et plus. Donc, si E.state est indexé, notre processeur de requêtes rompt l'indexation de E.age. Sélectionnez et fusionne l'élément E.state = 'Delaware' avec l'élément Scanner pour le transformer en un système beaucoup plus efficace Index Scan .

Disons dans cet exemple qu'il n'y a pas d'index sur E.state et E.age. La combinaison Sélectionnez a lieu après le "Scan" séquentiel de l'employé. Est-ce que cela fait une différence de savoir quelle condition dans le Sélectionnez est fait en premier ? Probablement pas beaucoup. Le processeur de requêtes pourrait les laisser dans l'ordre original de l'instruction SQL, ou bien il pourrait être un peu plus sophistiqué et examiner les dépenses attendues. D'après les statistiques, il constaterait à nouveau que la condition E.state = 'Delaware' devrait être plus sélective. Il inverserait donc les conditions et la ferait en premier, de sorte qu'il n'y aurait que 126 comparaisons E.age > 21 au lieu de 5 000. Ou encore, il pourrait se rendre compte que les comparaisons d'égalité de chaînes de caractères sont beaucoup plus coûteuses que les comparaisons d'entiers et ne pas modifier l'ordre.

Quoi qu'il en soit, tout ceci est très complexe et il est très peu probable que l'ordre de vos conditions syntaxiques fasse une différence. Je ne m'en préoccuperais pas, à moins que vous n'ayez un réel problème de performances et que votre fournisseur de base de données utilise l'ordre des conditions comme une indication.

12voto

Andomar Points 115404

La plupart des optimiseurs de requêtes utilisent l'ordre dans lequel les conditions apparaissent comme un indice. Si tout le reste est égal, ils suivront cet ordre.

Cependant, beaucoup de choses peuvent passer outre :

  • le deuxième champ a un index et le premier n'en a pas.
  • il existe des statistiques qui suggèrent que le champ 2 est plus sélectif
  • le second champ est plus facile à rechercher ( varchar(max) vs int )

Ainsi (et ceci est vrai pour toutes les questions d'optimisation SQL), à moins que vous n'observiez un problème de performances, il est préférable d'optimiser pour la clarté, et non pour les performances (supposées).

7voto

Cfreak Points 10831

Il ne devrait pas dans votre petit exemple. L'optimiseur de requêtes devrait faire ce qu'il faut. Vous pouvez en être sûr en ajoutant explain au début de la requête. MySQL vous dira comment il joint les choses et combien de lignes il doit rechercher pour effectuer la jointure. Par exemple :

explain select * from table where type=1 and userid=5

S'ils n'étaient pas indexés, cela changerait probablement le comportement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X