INNER JOIN vs performances LEFT JOIN dans SQL Server


259

J'ai créé une commande SQL qui utilise INNER JOIN sur 9 tables, de toute façon cette commande prend très longtemps (plus de cinq minutes). Donc mes gens m'ont suggéré de changer INNER JOIN en LEFT JOIN parce que les performances de LEFT JOIN sont meilleures, malgré ce que je sais. Après l'avoir changé, la vitesse de la requête s'est considérablement améliorée.

Je voudrais savoir pourquoi LEFT JOIN est plus rapide que INNER JOIN?

Ma commande SQL ressemble à ci-dessous: SELECT * FROM A INNER JOIN B ON ... INNER JOIN C ON ... INNER JOIN Det ainsi de suite

Mise à jour: Ceci est bref de mon schéma.

FROM sidisaleshdrmly a -- NOT HAVE PK AND FK
    INNER JOIN sidisalesdetmly b -- THIS TABLE ALSO HAVE NO PK AND FK
        ON a.CompanyCd = b.CompanyCd 
           AND a.SPRNo = b.SPRNo 
           AND a.SuffixNo = b.SuffixNo 
           AND a.dnno = b.dnno
    INNER JOIN exFSlipDet h -- PK = CompanyCd, FSlipNo, FSlipSuffix, FSlipLine
        ON a.CompanyCd = h.CompanyCd
           AND a.sprno = h.AcctSPRNo
    INNER JOIN exFSlipHdr c -- PK = CompanyCd, FSlipNo, FSlipSuffix
        ON c.CompanyCd = h.CompanyCd
           AND c.FSlipNo = h.FSlipNo 
           AND c.FSlipSuffix = h.FSlipSuffix 
    INNER JOIN coMappingExpParty d -- NO PK AND FK
        ON c.CompanyCd = d.CompanyCd
           AND c.CountryCd = d.CountryCd 
    INNER JOIN coProduct e -- PK = CompanyCd, ProductSalesCd
        ON b.CompanyCd = e.CompanyCd
           AND b.ProductSalesCd = e.ProductSalesCd 
    LEFT JOIN coUOM i -- PK = UOMId
        ON h.UOMId = i.UOMId 
    INNER JOIN coProductOldInformation j -- PK = CompanyCd, BFStatus, SpecCd
        ON a.CompanyCd = j.CompanyCd
            AND b.BFStatus = j.BFStatus
            AND b.ProductSalesCd = j.ProductSalesCd
    INNER JOIN coProductGroup1 g1 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup1Cd
        ON e.ProductGroup1Cd  = g1.ProductGroup1Cd
    INNER JOIN coProductGroup2 g2 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup2Cd
        ON e.ProductGroup1Cd  = g2.ProductGroup1Cd

1
Projetez-vous un attribut à partir de coUOM? Sinon, vous pourrez peut-être utiliser une semi-jointure. Si oui, vous pourriez l'utiliser UNIONcomme alternative. Publier uniquement votre FROMclause est une information inadéquate ici.
quand

1
Je me le demande si souvent (parce que je vois tout le temps).
Paul Draper

1
Avez-vous manqué une commande par dans votre bref schéma? J'ai récemment rencontré un problème où le changement d'une jointure interne en jointure externe gauche accélère la requête de 3 minutes à 10 secondes. Si vous avez vraiment Order By dans votre requête, je vais vous expliquer plus en détail comme réponse. Il semblait que toutes les réponses n'expliquaient pas vraiment le cas auquel j'étais confronté.
Phuah Yee Keat du

Réponses:


403

A LEFT JOINn'est absolument pas plus rapide que an INNER JOIN. En fait, c'est plus lent; par définition, une jointure externe ( LEFT JOINou RIGHT JOIN) doit faire tout le travail d'un INNER JOINplus le travail supplémentaire d'extension nulle des résultats. Il devrait également renvoyer plus de lignes, augmentant encore le temps d'exécution total simplement en raison de la plus grande taille de l'ensemble de résultats.

(Et même si a LEFT JOIN était plus rapide dans des situations spécifiques en raison d'une confluence de facteurs difficile à imaginer, il n'est pas fonctionnellement équivalent à un INNER JOIN, vous ne pouvez donc pas simplement remplacer toutes les instances de l'une par l'autre!)

Vos problèmes de performances se situent probablement ailleurs, comme le fait de ne pas avoir une clé candidate ou une clé étrangère indexée correctement. 9 tables, c'est beaucoup de choses à rejoindre, donc le ralentissement pourrait littéralement être presque n'importe où. Si vous publiez votre schéma, nous pourrons peut-être vous fournir plus de détails.


Éditer:

En réfléchissant davantage à cela, je pourrais penser à une circonstance dans laquelle un LEFT JOINpourrait être plus rapide qu'un INNER JOIN, et c'est quand:

  • Certaines tables sont très petites (disons, moins de 10 lignes);
  • Les tables n'ont pas d'index suffisants pour couvrir la requête.

Considérez cet exemple:

CREATE TABLE #Test1
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test1 (ID, Name) VALUES (1, 'One')
INSERT #Test1 (ID, Name) VALUES (2, 'Two')
INSERT #Test1 (ID, Name) VALUES (3, 'Three')
INSERT #Test1 (ID, Name) VALUES (4, 'Four')
INSERT #Test1 (ID, Name) VALUES (5, 'Five')

CREATE TABLE #Test2
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test2 (ID, Name) VALUES (1, 'One')
INSERT #Test2 (ID, Name) VALUES (2, 'Two')
INSERT #Test2 (ID, Name) VALUES (3, 'Three')
INSERT #Test2 (ID, Name) VALUES (4, 'Four')
INSERT #Test2 (ID, Name) VALUES (5, 'Five')

SELECT *
FROM #Test1 t1
INNER JOIN #Test2 t2
ON t2.Name = t1.Name

SELECT *
FROM #Test1 t1
LEFT JOIN #Test2 t2
ON t2.Name = t1.Name

DROP TABLE #Test1
DROP TABLE #Test2

Si vous l'exécutez et affichez le plan d'exécution, vous verrez que la INNER JOINrequête coûte en effet plus cher que le LEFT JOIN, car elle satisfait les deux critères ci-dessus. C'est parce que SQL Server veut faire une correspondance de hachage pour le INNER JOIN, mais fait des boucles imbriquées pour le LEFT JOIN; le premier est normalement beaucoup plus rapide, mais comme le nombre de lignes est si petit et qu'il n'y a pas d'index à utiliser, l'opération de hachage s'avère être la partie la plus coûteuse de la requête.

Vous pouvez voir le même effet en écrivant un programme dans votre langage de programmation préféré pour effectuer un grand nombre de recherches sur une liste à 5 éléments, contre une table de hachage à 5 éléments. En raison de la taille, la version de la table de hachage est en fait plus lente. Mais augmentez-le à 50 éléments, ou 5000 éléments, et la version de liste ralentit à une analyse, car c'est O (N) vs O (1) pour la table de hachage.

Mais changez cette requête pour qu'elle soit sur la IDcolonne au lieu de Nameet vous verrez une histoire très différente. Dans ce cas, il fait des boucles imbriquées pour les deux requêtes, mais la INNER JOINversion est capable de remplacer l'une des analyses d'index cluster avec une recherche - ce qui signifie que ce sera littéralement un ordre de grandeur plus rapide avec un grand nombre de lignes.

La conclusion est donc plus ou moins celle que j'ai mentionnée plusieurs paragraphes ci-dessus; il s'agit presque certainement d'un problème d'indexation ou de couverture d'index, éventuellement associé à une ou plusieurs très petites tables. Ce sont les seules circonstances dans lesquelles SQL Server peut parfois choisir un plan d'exécution pire pour un INNER JOINque pour un LEFT JOIN.


4
Il existe un autre scénario qui peut conduire à un OUTER JOIN performant mieux qu'un INNER JOIN. Voir ma réponse ci-dessous.
dbenham

12
Je tiens à souligner qu'il n'y a fondamentalement aucune documentation de base de données pour soutenir l'idée que les performances des jointures internes et des jointures externes sont différentes. Les jointures externes sont légèrement plus chères que les jointures internes, en raison du volume des données et de la taille de l'ensemble de résultats. Cependant, les algorithmes sous-jacents ( msdn.microsoft.com/en-us/library/ms191426(v=sql.105).aspx ) sont les mêmes pour les deux types de jointures. Les performances doivent être similaires lorsqu'elles renvoient des quantités de données similaires.
Gordon Linoff

3
@Aaronaught. . . Cette réponse a été référencée dans un commentaire qui disait quelque chose à l'effet que "les jointures externes fonctionnent bien moins que les jointures internes". J'ai commenté juste pour être sûr que cette mauvaise interprétation ne se propage pas.
Gordon Linoff

16
Je pense que cette réponse est trompeuse sur un aspect important: car elle indique "UNE JOINTE GAUCHE n'est absolument pas plus rapide qu'une JOINTE INTERNE". Cette ligne n'est pas correcte. Il n'est théoriquement pas plus rapide qu'un INNER JOIN. Ce n'est PAS "absolument pas plus rapide". La question est spécifiquement une question de performance. Dans la pratique, j'ai maintenant vu quelques systèmes (par de très grandes entreprises!) Où INNER JOIN était ridiculement lent par rapport à OUTER JOIN. La théorie et la pratique sont des choses très différentes.
David Frenkel

5
@DavidFrenkel: C'est hautement improbable. Je demanderais à voir une comparaison A / B, avec des plans d'exécution, si vous pensez qu'une telle divergence est possible. Elle est peut-être liée à des plans de requête / exécution mis en cache ou à de mauvaises statistiques.
Aaronaught

127

Il existe un scénario important qui peut conduire à une jointure externe plus rapide qu'une jointure interne qui n'a pas encore été discutée.

Lors de l'utilisation d'une jointure externe, l'optimiseur est toujours libre de supprimer la table jointe externe du plan d'exécution si les colonnes de jointure sont le PK de la table externe et qu'aucune des colonnes de table externe n'est référencée en dehors de la jointure externe elle-même. Par exemple, SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEYet B.KEY est le PK pour B. Oracle (je crois que j'utilisais la version 10) et Sql Server (j'ai utilisé 2008 R2) élaguent la table B du plan d'exécution.

La même chose n'est pas nécessairement vraie pour une jointure interne: SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEYpeut ou peut ne pas nécessiter B dans le plan d'exécution en fonction des contraintes existantes.

Si A.KEY est une clé étrangère nullable faisant référence à B.KEY, l'optimiseur ne peut pas supprimer B du plan car il doit confirmer qu'une ligne B existe pour chaque ligne A.

Si A.KEY est une clé étrangère obligatoire référençant B.KEY, l'optimiseur est libre de supprimer B du plan car les contraintes garantissent l'existence de la ligne. Mais ce n'est pas parce que l'optimiseur peut supprimer la table du plan. SQL Server 2008 R2 ne supprime PAS B du plan. Oracle 10 supprime B du plan. Il est facile de voir comment la jointure externe surpassera la jointure interne sur SQL Server dans ce cas.

Il s'agit d'un exemple trivial et non pratique pour une requête autonome. Pourquoi rejoindre une table si vous n'en avez pas besoin?

Mais cela pourrait être une considération de conception très importante lors de la conception de vues. Souvent, une vue «tout faire» est créée qui joint tout ce dont un utilisateur peut avoir besoin en rapport avec une table centrale. (Surtout s'il y a des utilisateurs naïfs effectuant des requêtes ad hoc qui ne comprennent pas le modèle relationnel) La vue peut inclure toutes les colonnes pertinentes de nombreuses tables. Mais les utilisateurs finaux peuvent uniquement accéder aux colonnes d'un sous-ensemble des tables dans la vue. Si les tables sont jointes avec des jointures externes, l'optimiseur peut (et supprime) les tables inutiles du plan.

Il est essentiel de s'assurer que la vue utilisant des jointures externes donne les résultats corrects. Comme Aaronaught l'a dit - vous ne pouvez pas substituer aveuglément OUTER JOIN à INNER JOIN et vous attendre aux mêmes résultats. Mais il peut arriver que cela soit utile pour des raisons de performances lors de l'utilisation des vues.

Une dernière note - je n'ai pas testé l'impact sur les performances à la lumière de ce qui précède, mais en théorie, il semble que vous devriez pouvoir remplacer en toute sécurité un INNER JOIN par un OUTER JOIN si vous ajoutez également la condition <FOREIGN_KEY> IS NOT NULL à la clause where.


5
En fait, j'ai rencontré ce problème lors de la création de requêtes extrêmement dynamiques. J'avais laissé dans un INNER JOIN que j'utilisais et ne tirais pas de données, et quand je l'ai changé pour un LEFT JOIN (par curiosité de cisaillement), la requête s'est réellement déroulée plus rapidement.
Erik Philips

1
EDIT - Clarification des conditions qui doivent exister pour que l'optimiseur supprime la table jointe externe du plan d'exécution.
dbenham

2
Une précision mineure à votre réponse: lorsque la colonne de clé étrangère n'est pas nullable, l'INNER JOIN et le LEFT JOIN deviennent sémantiquement équivalents (c'est-à-dire que votre clause WHERE suggérée est redondante); la seule différence serait le plan d'exécution.
Douglas

2
Bien que cela montre un exemple apparemment trivial, c'est une réponse extraordinairement perspicace!
pbalaga

6
+1: Il semble que j'aie rencontré cela dans quelques requêtes où j'utilisais des jointures internes avec de très grandes tables. La jointure interne provoquait un déversement dans tempdb dans le plan de requête (je suppose pour la raison indiquée ci-dessus - et mon serveur manquant de RAM pour tout garder en mémoire). Passer aux jointures gauches a éliminé le déversement sur tempdb, ce qui fait que certaines de mes requêtes de 20 à 30 secondes s'exécutent maintenant en quelques fractions de seconde. C'est un problème très important, car la plupart des gens semblent supposer que les jointures internes sont plus rapides.
phosplait

23

Si tout fonctionne comme il le devrait, MAIS nous savons tous que tout ne fonctionne pas comme il le devrait, en particulier en ce qui concerne l'optimiseur de requêtes, la mise en cache du plan de requête et les statistiques.

Tout d'abord, je suggérerais de reconstruire l'index et les statistiques, puis de vider le cache du plan de requête juste pour vous assurer que cela ne gâche rien. Cependant, j'ai rencontré des problèmes même lorsque cela est fait.

J'ai rencontré des cas où une jointure gauche a été plus rapide qu'une jointure interne.

La raison sous-jacente est la suivante: si vous avez deux tables et que vous vous joignez à une colonne avec un index (sur les deux tables). La jointure interne produira le même résultat, peu importe si vous bouclez sur les entrées de l'index sur la table un et faites correspondre avec l'index sur la table deux comme si vous faisiez l'inverse: Bouclez les entrées dans l'index sur la table deux et faites correspondre avec l'index dans le tableau un. Le problème est que lorsque vous avez des statistiques trompeuses, l'optimiseur de requête utilisera les statistiques de l'index pour trouver la table avec les entrées les moins correspondantes (en fonction de vos autres critères). Si vous avez deux tableaux avec 1 million chacun, dans le tableau un, vous avez 10 lignes correspondant et dans le tableau deux, vous avez 100000 lignes correspondant. La meilleure façon serait de faire un balayage d'index sur la table un et de faire correspondre 10 fois dans la table deux. L'inverse serait un balayage d'index qui boucle sur 100 000 lignes et essaie de correspondre 100 000 fois et seulement 10 réussissent. Donc, si les statistiques ne sont pas correctes, l'optimiseur peut choisir la mauvaise table et l'index à boucler.

Si l'optimiseur choisit d'optimiser la jointure gauche dans l'ordre d'écriture, il fonctionnera mieux que la jointure interne.

MAIS, l'optimiseur peut également optimiser une jointure gauche de manière sous-optimale en tant que semi-jointure gauche. Pour le faire, choisissez celui que vous voulez, vous pouvez utiliser l'indicateur d'ordre de force.


18

Essayez les deux requêtes (celle avec jointure interne et gauche) avec OPTION (FORCE ORDER)à la fin et publiez les résultats. OPTION (FORCE ORDER)est un indice de requête qui force l'optimiseur à créer le plan d'exécution avec l'ordre de jointure que vous avez fourni dans la requête.

Si INNER JOINcommence à jouer aussi vite que possible LEFT JOIN, c'est parce que:

  • Dans une requête composée entièrement de INNER JOINs, l'ordre de jointure n'a pas d'importance. L'optimiseur de requêtes peut ainsi ordonner les jointures comme bon lui semble, de sorte que le problème peut dépendre de l'optimiseur.
  • Avec LEFT JOIN, ce n'est pas le cas, car la modification de l'ordre de jointure modifiera les résultats de la requête. Cela signifie que le moteur doit suivre l'ordre de jointure que vous avez fourni dans la requête, qui pourrait être meilleur que celui optimisé.

Je ne sais pas si cela répond à votre question, mais j'étais une fois dans un projet qui comportait des requêtes très complexes faisant des calculs, ce qui a complètement gâché l'optimiseur. Nous avons eu des cas où un FORCE ORDERréduirait le temps d'exécution d'une requête de 5 minutes à 10 secondes.


9

Ont fait un certain nombre de comparaisons entre les jointures externes et internes gauches et n'ont pas été en mesure de trouver une différence consisten. Il existe de nombreuses variables. Je travaille sur une base de données de rapports avec des milliers de tables dont beaucoup avec un grand nombre de champs, de nombreux changements au fil du temps (versions des fournisseurs et workflow local). Il n'est pas possible de créer toutes les combinaisons d'index de couverture pour répondre aux besoins d'une si grande variété de requêtes et gérer les données historiques. Des requêtes internes ont tué les performances du serveur car deux grandes tables (des millions à des dizaines de millions de lignes) sont jointes en interne, tirant toutes les deux un grand nombre de champs et aucun index de couverture n'existe.

Le plus gros problème, cependant, ne semble pas apparaître dans les discussions ci-dessus. Peut-être que votre base de données est bien conçue avec des déclencheurs et un traitement des transactions bien conçu pour garantir de bonnes données. La mienne a souvent des valeurs NULL là où elles ne sont pas attendues. Oui, les définitions de table pourraient appliquer des valeurs Null, mais ce n'est pas une option dans mon environnement.

La question est donc ... concevez-vous votre requête uniquement pour la vitesse, une priorité plus élevée pour le traitement des transactions qui exécute le même code des milliers de fois par minute. Ou optez-vous pour la précision qu'une jointure externe gauche fournira. N'oubliez pas que les jointures internes doivent trouver des correspondances des deux côtés, de sorte qu'un NULL inattendu supprimera non seulement les données des deux tables mais également des lignes d'informations entières. Et ça se passe si bien, pas de messages d'erreur.

Vous pouvez être très rapide car obtenir 90% des données nécessaires et ne pas découvrir que les jointures internes ont supprimé silencieusement les informations. Parfois, les jointures internes peuvent être plus rapides, mais je ne crois pas que quiconque fasse cette hypothèse à moins d'avoir examiné le plan d'exécution. La vitesse est importante, mais la précision est plus importante.


8

Vos problèmes de performances sont plus susceptibles d'être dus au nombre de jointures que vous effectuez et si les colonnes sur lesquelles vous vous joignez ont des index ou non.

Dans le pire des cas, vous pourriez facilement effectuer 9 analyses de table entières pour chaque jointure.


7

Les jointures externes peuvent offrir des performances supérieures lorsqu'elles sont utilisées dans les vues.

Supposons que vous ayez une requête qui implique une vue, et que cette vue se compose de 10 tables réunies. Supposons que votre requête n'utilise que des colonnes de 3 de ces 10 tables.

Si ces 10 tables avaient été jointes ensemble, l'optimiseur de requête devrait les joindre toutes, même si votre requête elle-même n'a pas besoin de 7 sur 10 des tables. En effet, les jointures internes elles-mêmes peuvent filtrer les données, ce qui les rend essentielles pour le calcul.

Si ces 10 tables avaient été jointes à l'extérieur à la place, l'optimiseur de requête ne rejoindrait en fait que celles qui étaient nécessaires: 3 sur 10 dans ce cas. En effet, les jointures elles-mêmes ne filtrent plus les données et les jointures inutilisées peuvent donc être ignorées.

Source: http://www.sqlservercentral.com/blogs/sql_coach/2010/07/29/poor-little-misunderstood-views/


1
Votre déclaration sur "jointure externe" est trompeuse et potentiellement incorrecte. Outer signifie que les données de l'autre côté n'ont pas besoin d'exister - et si elles ne remplacent pas NULL. Dans des circonstances spécifiques, le SGBDR peut les "sauter" (voir la réponse ci-dessus de dbenham). CEPENDANT - externe vs interne peut faire en sorte que votre requête renvoie des résultats radicalement différents. INNER signifie - donne les résultats pour lesquels un élément est dans les deux A & B. LEFT OUTER signifie tout A, et éventuellement B s'il existe. Premier cas - vous obtenez quelques lignes, dans le second, vous obtenez TOUTES les lignes.
ripvlan

1
@ripvlan Bien sûr, les jointures externes et internes ne sont pas toujours interchangeables. La question initiale portait sur les performances, ce qui implique que nous parlons de cas dans lesquels l'une ou l'autre jointure retournerait le même ensemble de résultats.
MarredCheese

1
Oui et - OUTER peut entraîner un problème de performances car il entraînera le retour de toutes les lignes (plus de données). Votre hypothèse selon laquelle les requêtes aboutissent à la même sortie est juste - mais ce n'est pas vrai dans le cas général et spécifique à chaque conception de base de données. Et pour ceux qui ne connaissent pas à 100% l'algèbre relationnelle, cela pourrait leur causer du chagrin. Mon point est seulement d'offrir plus de perspicacité aux personnes lisant ceci à la recherche de conseils et qu'un GAUCHE / DROITE ne résoudra pas comme par magie un problème et pourrait causer plus de problèmes. C'est une puissance restante pour le niveau 300 :-)
ripvlan

2

J'ai trouvé quelque chose d'intéressant dans SQL Server en vérifiant si les jointures internes sont plus rapides que les jointures gauches.

Si vous n'incluez pas les éléments de la table jointe gauche, dans l'instruction select, la jointure gauche sera plus rapide que la même requête avec jointure interne.

Si vous incluez la table jointe gauche dans l'instruction select, la jointure interne avec la même requête était égale ou plus rapide que la jointure gauche.


0

D'après mes comparaisons, je trouve qu'ils ont exactement le même plan d'exécution. Il existe trois scénarios:

  1. Si et quand ils retournent les mêmes résultats, ils ont la même vitesse. Cependant, nous devons garder à l'esprit qu'il ne s'agit pas des mêmes requêtes, et que LEFT JOIN renverra éventuellement plus de résultats (lorsque certaines conditions ON ne sont pas remplies) --- c'est pourquoi c'est généralement plus lent.

  2. Lorsque la table principale (la première non constante dans le plan d'exécution) a une condition restrictive (WHERE id =?) Et que la condition ON correspondante est sur une valeur NULL, la table "droite" n'est pas jointe --- c'est quand LEFT JOIN est plus rapide.

  3. Comme indiqué au point 1, INNER JOIN est généralement plus restrictif et renvoie moins de résultats et est donc plus rapide.

Les deux utilisent (les mêmes) indices.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.