Sous-requêtes vs jointures

158

J'ai refactoré une section lente d'une application héritée d'une autre société pour utiliser une jointure interne au lieu d'une sous-requête comme:

WHERE id IN (SELECT id FROM ...)

La requête refactorisée s'exécute environ 100 fois plus vite. (~ 50 secondes à ~ 0,3) Je m'attendais à une amélioration, mais est-ce que quelqu'un peut expliquer pourquoi c'était si radical? Les colonnes utilisées dans la clause where ont toutes été indexées. SQL exécute-t-il la requête dans la clause where une fois par ligne ou quelque chose?

Mise à jour - Expliquez les résultats:

La différence se situe dans la deuxième partie de la requête "where id in ()" -

2   DEPENDENT SUBQUERY  submission_tags ref st_tag_id   st_tag_id   4   const   2966    Using where

vs 1 ligne indexée avec la jointure:

    SIMPLE  s   eq_ref  PRIMARY PRIMARY 4   newsladder_production.st.submission_id  1   Using index
Palmsey
la source
4
Double
2
Pas un double. Cette question concerne spécifiquement la différence de performance frappante. L'autre question est plus générale, ouverte sur les avantages et les inconvénients de chaque approche et pourquoi une approche semble plus populaire.
Basil Bourque
@simhumileco Ce n'est pas une amélioration, ce n'est pas une différence, c'est contraire à ce que l'auteur a écrit et ce genre de modification du style de code est inapproprié. Quand dois-je modifier le code?
philipxy
Salut @philipxy, je n'avais pas l'intention de m'immiscer dans la pensée de l'auteur, mais seulement de rendre le fragment de code plus lisible et écrit avec plus de soin.
simhumileco

Réponses:

160

Une "sous-requête corrélée" (c'est-à-dire une dans laquelle la condition où dépend des valeurs obtenues à partir des lignes de la requête contenant) s'exécutera une fois pour chaque ligne. Une sous-requête non corrélée (une dans laquelle la condition where est indépendante de la requête contenant) s'exécutera une fois au début. Le moteur SQL fait cette distinction automatiquement.

Mais, ouais, expliquer-plan vous donnera les détails sales.

Jeffrey L Whitledge
la source
3
Veuillez noter que cela DEPENDENT SUBQUERYsignifie exactement la même chose que "sous-requête corrélée".
Timo
38

Vous exécutez la sous-requête une fois pour chaque ligne alors que la jointure se produit sur les index.

Sklivvz
la source
5
Je ne pense pas que ce soit vrai. Le moteur SQL ne doit exécuter la sous-requête qu'une seule fois et utiliser le résultat sous forme de liste.
dacracot le
8
Cela dépend - si la sous-requête est corrélée d'une manière ou d'une autre avec la requête externe (utilise ses données), elle est exécutée avec chaque ligne.
qbeuek le
4
C'est probablement vrai dans ce cas, mais ce n'est pas vrai en général.
Amy B du
1
OP EXPLAINdit DEPENDENT SUBQUERY, ce qui est l'indicateur le plus clair de ce comportement.
Timo
16

Voici un exemple de la façon dont les sous-requêtes sont évaluées dans MySQL 6.0 .

Le nouvel optimiseur convertira ce type de sous-requêtes en jointures.

Giuseppe Maxia
la source
C'est un excellent article sur l'optimiseur amélioré MySQL 6.0, merci
Fire Crow
7

Exécutez le plan d'explication sur chaque version, il vous dira pourquoi.

scotta
la source
6

avant que les requêtes ne soient exécutées sur l'ensemble de données, elles sont soumises à un optimiseur de requête, l'optimiseur tente d'organiser la requête de telle sorte qu'il puisse supprimer autant de tuples (lignes) de l'ensemble de résultats aussi rapidement que possible. Souvent, lorsque vous utilisez des sous-requêtes (en particulier les mauvaises), les tuples ne peuvent pas être supprimés du jeu de résultats tant que la requête externe ne démarre pas.

Sans voir la requête, il est difficile de dire ce qui était si mauvais à propos de l'original, mais je suppose que c'était quelque chose que l'optimiseur ne pouvait tout simplement pas faire beaucoup mieux. L'exécution de «Expliquer» vous montrera la méthode des optimiseurs pour récupérer les données.

Pfranza
la source
4

Regardez le plan de requête pour chaque requête.

Where in et Join peuvent généralement être implémentés en utilisant le même plan d'exécution, il n'y a donc généralement aucune accélération de changement entre eux.

Amy B
la source
3
Haha, I <3 Sql nettoie ce vote négatif parce qu'ils ne savent pas comment lire les plans de requête.
Amy B du
4

Optimizer n'a pas fait un très bon travail. Habituellement, ils peuvent être transformés sans aucune différence et l'optimiseur peut le faire.

Cade Roux
la source
4

Habituellement, c'est le résultat de l'incapacité de l'optimiseur à comprendre que la sous-requête peut être exécutée en tant que jointure, auquel cas il exécute la sous-requête pour chaque enregistrement de la table plutôt que de joindre la table dans la sous-requête par rapport à la table que vous interrogez. Certaines des bases de données les plus «d'entreprise» sont meilleures dans ce domaine, mais elles la manquent encore parfois.

Mark Roddy
la source
4

Cette question est quelque peu générale, voici donc une réponse générale:

Fondamentalement, les requêtes prennent plus de temps lorsque MySQL a des tonnes de lignes à trier.

Faites ceci:

Exécutez un EXPLAIN sur chacune des requêtes (celle jointe, puis la sous-requête) et publiez les résultats ici.

Je pense que voir la différence dans l'interprétation de MySQL de ces requêtes serait une expérience d'apprentissage pour tout le monde.

Pete Karl II
la source
4

La sous-requête where doit exécuter 1 requête pour chaque ligne renvoyée. La jointure interne doit simplement exécuter 1 requête.

Shawn
la source
3

La sous-requête exécutait probablement une "analyse complète de la table". En d'autres termes, ne pas utiliser l'index et renvoyer beaucoup trop de lignes que le lieu de la requête principale avait besoin de filtrer.

Juste une supposition sans détails bien sûr mais c'est la situation courante.

igelkott
la source
2

Avec une sous-requête, vous devez réexécuter le 2nd SELECT pour chaque résultat, et chaque exécution renvoie généralement 1 ligne.

Avec une jointure, le 2nd SELECT renvoie beaucoup plus de lignes, mais vous ne devez l'exécuter qu'une seule fois. L'avantage est que maintenant vous pouvez vous joindre aux résultats, et joindre des relations est ce à quoi une base de données est censée être bonne. Par exemple, l'optimiseur peut peut-être trouver comment tirer le meilleur parti d'un index maintenant.

Joël Coehoorn
la source
2

Ce n'est pas tant la sous-requête que la clause IN, bien que les jointures soient au moins à la base du moteur SQL d'Oracle et s'exécutent extrêmement rapidement.

dacracot
la source
1
où en vraiment n'est pas intrinsèquement mauvais.
Shawn du
2

Extrait du Manuel de référence ( 14.2.10.11 Réécriture des sous-requêtes en jointures ):

Une jointure gauche [externe] peut être plus rapide qu'une sous-requête équivalente car le serveur pourrait être en mesure de l'optimiser mieux - un fait qui n'est pas spécifique à MySQL Server seul.

Ainsi, les sous-requêtes peuvent être plus lentes que LEFT [OUTER] JOINS.

simhumileco
la source