Performances SQL Server IN et EXISTS

115

Je suis curieux de savoir lequel des éléments suivants serait le plus efficace?

J'ai toujours été un peu prudent dans l'utilisation INcar je pense que SQL Server transforme l'ensemble de résultats en une grande IFdéclaration. Pour un ensemble de résultats volumineux, cela peut entraîner de mauvaises performances. Pour les petits ensembles de résultats, je ne suis pas sûr que l'un ou l'autre soit préférable. Pour les grands ensembles de résultats, ne serait-il pas EXISTSplus efficace?

WHERE EXISTS (SELECT * FROM Base WHERE bx.BoxID = Base.BoxID AND [Rank] = 2)

contre.

WHERE bx.BoxID IN (SELECT BoxID FROM Base WHERE [Rank = 2])
Randy Minder
la source
8
La meilleure façon de le savoir est de l'essayer et de faire quelques mesures.
Klaus Byskov Pedersen le
10
il y a obtenu d'être un duplicata gazillion pour cette ......
marc_s
5
@marc_s - Probablement, mais dans le temps qu'il m'aurait fallu pour parcourir tous les articles sur ce sujet et en trouver un qui correspond à mon cas, j'ai eu quatre réponses à ma question.
Randy Minder le
7
Pour votre information, si vous voulez la manière la plus performante, vous pouvez le select 1 from Base...faire where existspuisque vous ne vous souciez pas réellement des résultats, juste qu'une ligne existe réellement.
brad
2
@marc_s c'est vraiment triste, car j'ai pris le temps de parcourir les messages afin de ne plus ajouter de corbeille à stackoverflow. Je n'ai pas besoin d'une réponse personnalisée pour faire mon travail. C'est le genre de réflexion qui a ajouté un doublon de Gazillion à la place de quelques-uns avec de bonnes réponses
IvoC

Réponses:

140

EXISTS sera plus rapide car une fois que le moteur a trouvé un coup, il cessera de regarder car la condition s'est avérée vraie.

Avec IN, il collectera tous les résultats de la sous-requête avant un traitement ultérieur.

keithwarren7
la source
4
C'est un bon point. L'instruction IN nécessite que SQL Server génère un jeu de résultats complet, puis crée une grande instruction IF, je pense.
Randy Minder le
72
Cela était vrai auparavant, mais dans les versions actuelles (au moins 2008), l'optimiseur est beaucoup plus intelligent ... il traite en fait IN () comme un EXISTS ().
Aaron Bertrand
11
@Aaron - oui, l'optimzer produira généralement un meilleur plan en interne. Cependant, s'appuyer sur des raccourcis internes peut être préjudiciable dans des scénarios plus complexes.
Scott Coates
2
C'est tout simplement faux. C'était en 2010 et l'est toujours.
Magnus
2
IN et EXISTS ont exactement le même plan de requête et IO. Il n'y a aucune raison de penser qu'ils ont des performances différentes. Vérifiez vos statistiques de temps et comprenez-vous
Nelssen
40

La réponse acceptée est myope et la question un peu lâche en cela:

1) Aucun des deux ne mentionne explicitement si un index de recouvrement est présent sur les côtés gauche, droit ou des deux côtés.

2) Ni l'un ni l'autre ne prend en compte la taille du jeu du côté gauche d'entrée et du jeu du côté droit de l'entrée.
(La question mentionne simplement un grand ensemble de résultats ).

Je pense que l'optimiseur est suffisamment intelligent pour convertir entre "in" et "existe" lorsqu'il y a une différence de coût significative due à (1) et (2), sinon il peut simplement être utilisé comme un indice (par exemple, il existe pour encourager l'utilisation de un index recherché sur le côté droit).

Les deux formulaires peuvent être convertis en formulaires de jointure en interne, avoir l'ordre de jointure inversé et s'exécuter en tant que boucle, hachage ou fusion - en fonction du nombre de lignes estimé (gauche et droite) et de l'existence d'index à gauche, à droite ou des deux côtés.

crokusek
la source
3
Je ne sais pas pourquoi cette excellente réponse n'a plus retenu l'attention. Comprendre l'indice / la structure des deux côtés pourrait avoir un impact, je suis d'accord. Bien dit.
SheldonH
L'optimiseur donne toujours le même plan pour INet EXISTS. Essayez de trouver tous les cas où ils n'obtiennent pas le même plan (bien que cela ne s'applique pas à NOT INet NOT EXISTS)
Martin Smith
@MartinSmith Je suppose que vous savez de quoi vous parlez, mais avez-vous des preuves que les plans sont toujours les mêmes? Si c'est le cas, cela éclaircirait le désaccord qui a duré une décennie ici.
MarredCheese
@MarredCheese - il incombe aux personnes qui prétendent qu'il est différent de produire un seul exemple de cela
Martin Smith
37

J'ai fait quelques tests sur SQL Server 2005 et 2008, et les EXISTS et IN reviennent avec exactement le même plan d'exécution réel, comme d'autres l'ont indiqué. L'optimiseur est optimal. :)

Cependant, il faut savoir que EXISTS, IN et JOIN peuvent parfois renvoyer des résultats différents si vous n'exprimez pas votre requête correctement: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210 .aspx

Adam Nofsinger
la source
5

Il y a beaucoup de réponses trompeuses ici, y compris celle hautement votée (bien que je ne pense pas que leurs opérations signifiaient du mal). La réponse courte est: ce sont les mêmes.

Il existe de nombreux mots-clés dans le langage (T-) SQL, mais à la fin, la seule chose qui se passe réellement sur le matériel, ce sont les opérations telles qu'elles apparaissent dans le plan de requête d'exécution.

L'opération relationnelle (théorie mathématique) que nous faisons lorsque nous invoquons [NOT] INet [NOT] EXISTSest la semi-jointure (anti-jointure lors de l'utilisation NOT). Ce n'est pas un hasard si les opérations sql-server correspondantes portent le même nom . Il n'y a aucune opération qui mentionne INou EXISTSn'importe où - seulement des (anti-) semi jointures. Ainsi, il n'y a aucun moyen qu'un choix de INvs logiquement équivalent EXISTSpuisse affecter les performances car il existe un seul et unique moyen, l'opération d'exécution (anti) semi-jointure, d'obtenir leurs résultats .

Un exemple:

Requête 1 ( plan )

select * from dt where dt.customer in (select c.code from customer c where c.active=0)

Requête 2 ( plan )

select * from dt where exists (select 1 from customer c where c.code=dt.customer and c.active=0)
George Menoutis
la source
L'avez-vous testé? Si oui, pouvez-vous partager votre SQL et vos résultats?
UnhandledExcepSean
Je l'ai testé plusieurs fois. Je peux créer un autre cas de test, et je le ferai, mais un cas de test ne signifie pas que l'optimiseur fera exactement le même plan sur des tables avec des statistiques différentes. Cela peut amener quelqu'un à penser que la réponse est partielle - mais la non-existence de plusieurs opérateurs de semi-jointure est un fait. Peut-être que je trouverai une liste quelque part et la lierai.
George Menoutis
5

J'irais avec EXISTS sur IN, voir le lien ci-dessous:

SQL Server: JOIN vs IN vs EXISTS - la différence logique

Il existe une idée fausse courante selon laquelle IN se comporte de la même manière que EXISTS ou JOIN en termes de résultats renvoyés. Ce n'est tout simplement pas vrai.

IN: renvoie true si une valeur spécifiée correspond à une valeur dans une sous-requête ou une liste.

Exists: renvoie true si une sous-requête contient des lignes.

Rejoindre: joint 2 jeux de résultats sur la colonne de jonction.

Crédit du blog: https://stackoverflow.com/users/31345/mladen-prajdic

Tanneur
la source
Wow, merci pour ton blog et explication.
Christian Müller
3

Les plans d'exécution seront généralement identiques dans ces cas, mais tant que vous ne verrez pas comment l'optimiseur prend en compte tous les autres aspects des index, etc., vous ne saurez jamais vraiment.

Cade Roux
la source
3

Ainsi, IN n'est pas identique à EXISTS et ne produira pas le même plan d'exécution.

Habituellement, EXISTS est utilisé dans une sous-requête corrélée, ce qui signifie que vous rejoindrez la requête interne EXISTS avec votre requête externe. Cela ajoutera d'autres étapes pour produire un résultat car vous devez résoudre les jointures de requête externes et les jointures de requête internes, puis faire correspondre leurs clauses where pour joindre les deux.

En général, IN est utilisé sans corréler la requête interne avec la requête externe, et cela peut être résolu en une seule étape (dans le meilleur des cas).

Considère ceci:

  1. Si vous utilisez IN et que le résultat de la requête interne est des millions de lignes de valeurs distinctes, il sera probablement plus lent que EXISTS étant donné que la requête EXISTS est performante (a les bons index à joindre avec la requête externe).

  2. Si vous utilisez EXISTS et que la jointure avec votre requête externe est complexe (prend plus de temps à effectuer, pas d'index approprié), cela ralentira la requête du nombre de lignes dans la table externe, parfois le temps estimé pour terminer peut être en jours. Si le nombre de lignes est acceptable pour votre matériel donné, ou si la cardinalité des données est correcte (par exemple moins de valeurs DISTINCT dans un grand ensemble de données), IN peut fonctionner plus rapidement que EXISTS.

  3. Tout ce qui précède sera noté lorsque vous avez une bonne quantité de lignes sur chaque table (par juste, je veux dire quelque chose qui dépasse votre traitement CPU et / ou les seuils de RAM pour la mise en cache).

Donc, la RÉPONSE est-elle DÉPEND. Vous pouvez écrire une requête complexe dans IN ou EXISTS, mais en règle générale, vous devriez essayer d'utiliser IN avec un ensemble limité de valeurs distinctes et EXISTS lorsque vous avez beaucoup de lignes avec beaucoup de valeurs distinctes.

L'astuce consiste à limiter le nombre de lignes à analyser.

Cordialement,

MarianoC

MarianoC
la source
1

Pour optimiser le EXISTS, soyez très littéral; quelque chose doit simplement être là, mais vous n'avez en fait pas besoin de données renvoyées par la sous-requête corrélée. Vous évaluez simplement une condition booléenne.

Alors:

WHERE EXISTS (SELECT TOP 1 1 FROM Base WHERE bx.BoxID = Base.BoxID AND [Rank] = 2)

Étant donné que la sous-requête corrélée est RBAR, le premier appel de résultat rend la condition vraie et elle n'est plus traitée.

Josh Lewis
la source
Je serais toujours extrêmement prudent en utilisant le codage LEFT JOIN + NULL, car il est très facile d'obtenir des résultats manqués ou biaisés si vous n'êtes pas très prudent dans votre gestion de NULL. J'ai très rarement trouvé une situation où EXISTS ou un CTE (pour trouver une duplication, ou une insertion synthétique pour des données manquantes), ne répondent pas aux mêmes exigences et surpassent le LEFT JOIN + NULL
Josh Lewis
3
TOP 1 doit être totalement étranger (ou redondant d'événement) lorsqu'il est utilisé avec EXISTS. EXISTS revient toujours dès qu'il trouve une ligne correspondante.
Karl Kieninger
Jusqu'à présent, je n'ai vu aucun avantage en termes de performances avec cette approche. Veuillez montrer quelques captures d'écran des plans d'exécution
DaFi4
-1

Du haut de ma tête et pas garanti d'être correct: je crois que le second sera plus rapide dans ce cas.

  1. Dans la première, la sous-requête corrélée entraînera probablement l'exécution de la sous-requête pour chaque ligne.
  2. Dans le deuxième exemple, la sous-requête ne doit s'exécuter qu'une seule fois, car elle n'est pas corrélée.
  3. Dans le deuxième exemple, le INcourt-circuitera dès qu'il trouve une correspondance.
RedFilter
la source