Envelopper la requête dans IF EXISTS la rend très lente


16

J'ai la requête ci-dessous:

select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)

La requête ci-dessus se termine en trois secondes.

Si la requête ci-dessus renvoie une valeur, nous voulons que la procédure stockée EXIT, donc je l'ai réécrite comme ci-dessous:

If Exists(
select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
)
Begin
Raiserror('Source missing',16,1)
Return
End

Cependant, cela prend 10 minutes.

Je peux réécrire la requête ci-dessus comme ci-dessous, qui se termine également en moins de 3 secondes:

  select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source
if @@rowcount >0
Begin
Raiserror('Source missing',16,1)
Return
End

Le problème avec la réécriture ci-dessus est que la requête ci-dessus fait partie d'une plus grande procédure stockée et renvoie plusieurs jeux de résultats. En C #, nous parcourons chaque jeu de résultats et effectuons un traitement.

Ce qui précède renvoie un jeu de résultats vide, donc si je choisis cette approche, je dois changer mon C # et refaire le déploiement.

Donc ma question est,

pourquoi l'utilisation IF EXISTSchange- t-elle simplement le plan pour prendre autant de temps?

Voici les détails qui peuvent vous aider et faites-moi savoir si vous avez besoin de détails:

  1. Créer un tableau et un script de statistiques pour obtenir le même plan que le mien
  2. Plan d'exécution lente
  3. Plan d'exécution rapide

    Plan lent avec Brentozar Coller le plan
    Plan rapide avec Brentozar Coller le plan

Remarque: Les deux requêtes sont les mêmes (en utilisant des paramètres), la seule différence est EXISTS(j'ai peut-être fait quelques erreurs lors de l'anonymat).

Les scripts de création de table sont ci-dessous:

http://pastebin.com/CgSHeqXc - statistiques de petite table
http://pastebin.com/GUu9KfpS - statistiques de grande table


La discussion sur cette question a été déplacée dans cette salle de discussion .
Paul White réintègre Monica

Réponses:


18

Comme cela a été expliqué par Paul White dans son blog: A l' intérieur de l'Optimiseur: Objectifs de ligne dans la profondeur les EXISTSde Introduit un but de la ligne, qui préfère NESTED LOOPSou MERGE JOINplusHASH MATCH

Comme dernier exemple, considérons qu'une semi-jointure logique (telle qu'une sous-requête introduite avec EXISTS) partage le thème général: elle doit être optimisée pour trouver rapidement la première ligne correspondante.

Dans votre requête, cela se produit apparemment pour introduire des boucles imbriquées et supprimer le parallélisme, ce qui entraîne un plan plus lent.

Il vous faudra donc probablement trouver un moyen de réécrire votre requête sans utiliser le à NOT EXISTSpartir de votre requête.

Vous pourriez vous en sortir en réécrivant votre requête à l'aide d'un LEFT OUTER JOINet en vérifiant qu'il n'y avait pas de ligne dans la petite table en testantNULL

If EXISTS(
    SELECT databasename
    FROM somedb.dbo.bigtable l
    LEFT JOIN dbo.smalltable c ON c.source = l.source
    WHERE databasename = 'someval'
    AND source <> 'kt'
    AND c.source IS NULL
)

Vous pouvez également utiliser une EXCEPTrequête, selon le nombre de champs sur lesquels vous devez comparer, comme ceci:

If EXISTS(
   SELECT source
   FROM somedb.dbo.bigtable l
   WHERE databasename = 'someval'
   AND source <> 'kt'

   EXCEPT

   SELECT source
   FROM dbo.smalltable
)

Attention, Aaron Bertrand a un article de blog expliquant pourquoi il préfère PAS EXISTE que vous devriez lire pour voir si d'autres approches fonctionnent mieux et pour être conscient des problèmes potentiels de correction en cas de valeurs NULL.

Questions et réponses connexes: SI EXISTE prend plus de temps que l'instruction select intégrée


0

Vous devez réécrire votre requête à l'aide de jointures explicites et spécifier l'opération de jointure que vous souhaitez utiliser (boucle, hachage ou fusion) comme celle-ci.

If not exists(
    select databasename 
    from somedb.dbo.bigtable l
    inner hash join dbo.smalltable c 
        on c.source = l.source
where databasename ='someval' and source  <>'kt')
begin
    Raiserror('Source missing',16,1)
    Return
end

Lorsque vous utilisez EXISTS ou NOT EXISTS, le plan de requête généré par SQL Server avec l'opération NESTED LOOP suppose qu'il doit parcourir une à une toutes les lignes de l'ensemble en recherchant la première ligne pour satisfaire la condition. L'utilisation de HASH JOIN accélérera.


Que vous le
testerez

0

J'ai rencontré le même problème, j'ai réussi à contourner le problème en évitant d'utiliser "EXISTS" et en utilisant la fonction "COUNT ()" et l'instruction "IF ... ELSE".

Pour votre exemple, essayez ce qui suit:

IF
(
    SELECT
        COUNT(l.databasename) + 1 AS databasename
    FROM somedb.dbo.bigtable AS l

    WHERE   l.databasename ='someval'
        AND l.[source]  <> 'kt'
        AND NOT EXISTS(SELECT 1 FROM dbo.smalltable AS c WHERE c.[source]=l.[source])
) > 1 --Acts like EXISTS
BEGIN
    RAISERROR('Source missing', 16, 1)
RETURN
END

La raison pour laquelle j'ajoute "+ 1" au nombre est pour que je puisse utiliser "> 1" dans la condition IF, l'utilisation de "> 0" ou "<> 0" déclenchera la requête pour utiliser des boucles imbriquées au lieu de HASH Rencontre. Je n'ai pas cherché à savoir pourquoi cela se produit exactement serait intéressant de savoir pourquoi.

J'espère que cela pourra aider!

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.