web-dev-qa-db-fra.com

INNER JOIN vs LEFT JOIN performances dans SQL Server

J'ai créé une commande SQL qui utilise INNER JOIN sur 9 tables. Quoi qu'il en soit, cette commande prend beaucoup de temps (plus de cinq minutes). Donc, mes gens m'ont suggéré de changer INNER JOIN en LEFT JOIN car la performance de LEFT JOIN est meilleure, malgré ce que je sais. Après l'avoir modifiée, 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 à celle ci-dessous: SELECT * FROM A INNER JOIN B ON ... INNER JOIN C ON ... INNER JOIN D et 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
243
Anonymous

Un LEFT JOIN n'est absolument pas plus rapide qu'un INNER JOIN. En fait, c'est plus lent. Par définition, une jointure externe (LEFT JOIN ou RIGHT JOIN) doit effectuer tout le travail d'un INNER JOIN plus le travail supplémentaire consistant à étendre les résultats à néant. Il devrait également renvoyer davantage de lignes, ce qui augmente encore le temps d'exécution total simplement en raison de la taille plus grande de l'ensemble de résultats.

(Et même si un LEFT JOIN était plus rapide en spécifique Dans certaines situations dues à 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’un par l’autre!)

Il est fort probable que vos problèmes de performances se situent ailleurs, par exemple, en l'absence d'une clé candidate ou d'une clé étrangère indexée correctement. 9 tables, c'est beaucoup à rejoindre alors le ralentissement pourrait littéralement être presque n'importe où. Si vous publiez votre schéma, nous pourrons peut-être fournir plus de détails.


Edit:

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

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

Considérons 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 exécutez ceci et affichez le plan d'exécution, vous verrez que la requête INNER JOIN coûte effectivement plus cher que le LEFT JOIN, car elle répond aux deux critères ci-dessus. C'est parce que SQL Server veut faire une correspondance de hachage pour le INNER JOIN, mais crée des boucles imbriquées pour le LEFT JOIN; le premier est normalement beaucoup plus rapide, mais comme le nombre de lignes est très petit et il n'y a pas d'index à utiliser, l'opération de hachage s'avère être la partie la plus chère 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 dans une liste à 5 éléments, par rapport à une table de hachage à 5 éléments. En raison de la taille, la version de la table de hachage est en réalité plus lente. Mais augmentez-le à 50 éléments, ou 5000 éléments, et la version de liste ralentit, car c'est O(N) vs. O(1) pour la table de hachage.

Mais changez cette requête pour qu'elle soit sur la colonne ID au lieu de Name et vous verrez une histoire très différente. Dans ce cas, les boucles sont imbriquées pour les deux requêtes, mais la version INNER JOIN peut remplacer l’un des balayages d’index en cluster par une recherche - ce qui signifie qu’il s’agira littéralement ordre de grandeur plus rapide avec un grand nombre de lignes.

Donc, la conclusion est plus ou moins ce que j'ai mentionné plusieurs paragraphes ci-dessus; il s'agit presque certainement d'un problème d'indexation ou de couverture d'index, éventuellement associé à un ou plusieurs très petits tableaux. Ce sont les seules circonstances dans lesquelles SQL Server pourrait choisir parfois un plan d’exécution plus mauvais pour un INNER JOIN qu'un LEFT JOIN.

385
Aaronaught

Il existe un scénario important pouvant 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 la clé de protection de la table externe et qu'aucune des colonnes n'est sélectionnée dans la table externe. Par exemple, SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEY et B.KEY sont les clés de commande de B. Oracle (je crois que j'utilisais la version 10) et Sql Server (j'ai utilisé 2008 R2) Prune, 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.KEY peut nécessiter ou non B dans le plan d'exécution, en fonction des contraintes existantes.

Si A.KEY est une clé étrangère nullable référençant 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 que ce sera le cas. SQL Server 2008 R2 ne supprime pas B du plan. Oracle 10 DOIT supprimer B du plan. Dans ce cas, il est facile de voir comment la jointure externe surpassera la jointure interne sur SQL Server.

Ceci est un exemple trivial et peu 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. Il est fréquent de créer une vue "tout faire" qui rassemble tout ce dont un utilisateur peut avoir besoin concernant une table centrale. (Surtout si des utilisateurs naïfs font 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 de la vue. Si les tables sont jointes avec des jointures externes, l'optimiseur peut (et supprime) les tables non nécessaires 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 aveuglément substituer OUTER JOIN à INNER JOIN et attendre les mêmes résultats. Mais il arrive parfois que cela soit utile pour des raisons de performances lors de l’utilisation de vues.

Une dernière remarque - je n’ai pas testé l’impact sur les performances à la lumière de ce qui précède, mais il semble théoriquement 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.

115
dbenham

Si tout fonctionne comme prévu, MAIS nous savons tous que tout ne fonctionne pas comme il se doit, notamment en ce qui concerne l'optimiseur de requêtes, la mise en cache du plan de requêtes 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 pour s'assurer que cela ne gâche rien. Cependant, j'ai eu des problèmes même quand c'est fait.

J'ai rencontré des cas dans lesquels une jointure gauche était 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 passez en boucle sur les entrées de l'index de la première table et que vous faites correspondre l'index de la deuxième table comme si vous procédiez de la manière inverse: Boucle sur les entrées de l'index de la deuxième table et la correspondance avec l'index dans la première table. Le problème est que lorsque vous avez des statistiques trompeuses, l'optimiseur de requêtes 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 tables de 1 million chacune, dans la première table, vous avez 10 lignes correspondantes et dans la deuxième table, 100 000 lignes correspondantes. Le meilleur moyen serait de faire un balayage d'index sur la table un et de faire correspondre 10 fois la table deux. L'inverse serait une analyse d'index qui boucle plus de 100 000 lignes et tente de faire correspondre 100 000 fois et seulement 10 réussissent. Ainsi, si les statistiques ne sont pas correctes, l'optimiseur peut choisir la table et l'index sur lesquels effectuer la boucle.

Si l'optimiseur choisit d'optimiser la jointure gauche dans l'ordre indiqué, ses performances seront meilleures que celles de la jointure interne.

MAIS, l’optimiseur peut également optimiser une jointure gauche sous-optimale sous la forme d’une jointure gauche. Pour lui faire choisir celui que vous voulez, vous pouvez utiliser l'indicateur d'ordre de force.

22
Kvasi

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

Si INNER JOIN commence à fonctionner aussi vite que LEFT JOIN, c'est parce que:

  • Dans une requête composée entièrement par INNER JOINs, l'ordre de jointure n'a pas d'importance. Cela donne à l'optimiseur de requêtes la liberté de classer les jointures comme bon lui semble. Le problème peut donc s'appuyer sur 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 respecter l'ordre de jointure indiqué dans la requête, qui peut être supérieur à celui optimisé.

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

17
Francisco Pires

Vous avez effectué un certain nombre de comparaisons entre les jointures externes et internes de gauche et vous n’avez pas réussi à trouver une différence de cohérence. Il y a beaucoup de variables. Travaillez sur une base de données de rapports contenant des milliers de tables, plusieurs avec un grand nombre de champs et de nombreuses modifications au fil du temps (versions du fournisseur et flux de travail local). Il n'est pas possible de créer toutes les combinaisons d'index de couverture pour répondre aux besoins d'une aussi grande variété de requêtes et gérer les données historiques. Nous avons vu des requêtes internes compromettre les performances du serveur, car deux tables volumineuses (des millions à des dizaines de millions de lignes) sont jointes de manière interne en extrayant un grand nombre de champs et en l'absence d'un index couvrant.

Le plus gros problème ne semble cependant 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 assurer de bonnes données. Le mien a souvent des valeurs NULL où elles ne sont pas attendues. Oui, les définitions de table pourraient appliquer no-Nulls, 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 allez-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. Par conséquent, une valeur NULL inattendue supprimera non seulement les données des deux tables, mais éventuellement des lignes entières d'informations. Et cela se passe si bien, pas de message d'erreur.

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

8
J.O.

Vos problèmes de performances sont probablement dus au nombre de jointures que vous effectuez et au fait que les colonnes que vous rejoignez aient des index ou non.

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

7
eddiegroves

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

Supposons que votre requête implique une vue et que cette vue soit composée de 10 tables jointes. Supposons que votre requête utilise uniquement des colonnes provenant de 3 de ces 10 tables.

Si ces 10 tables avaient été jointes en interne ensemble, alors l'optimiseur de requête devrait les joindre toutes, même si votre requête elle-même n'avait pas besoin de 7 sorties. de 10 des tables. En effet, les jointures internes elles-mêmes peuvent filtrer les données, ce qui les rend indispensables au calcul.

Si ces 10 tables avaient été reliées par une jonction externe ensemble, l'optimiseur de requête ne rejoindrait en réalité que celles qui étaient nécessaires: 3 sur 10 d'entre elles. 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/

5
MarredCheese

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

Si vous n'incluez pas les éléments de la table jointe à gauche, dans la requête 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 contenant la même requête était égale ou supérieure à la jointure gauche.

2
Buzzzzzzz