web-dev-qa-db-fra.com

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

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 utiliser simplement IF EXISTS modifie 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 utilisant Brentozar Collez le plan
    Plan rapide utilisant 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 en anonymisant cependant ).

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

http://Pastebin.com/CgSHeqXc - statistiques des petites tables
http://Pastebin.com/GUu9KfpS - statistiques de grande table

16
TheGameiswar

Comme cela a été expliqué par Paul White dans son article de blog: Inside the Optimizer: Row Goals In Depth le EXISTS introduit un objectif de ligne, qui préfère NESTED LOOPS ou MERGE JOIN plus de HASH 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.

Vous devrez donc probablement trouver un moyen de réécrire votre requête sans utiliser le NOT EXISTS de votre requête.

Vous pourriez vous en sortir en réécrivant votre requête à l'aide d'un LEFT OUTER JOIN et en vérifiant qu'il n'y avait pas de ligne dans smalltable en testant NULL

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 pourriez probablement aussi utiliser une requête EXCEPT, 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 fournissant les raisons pour lesquelles il préfère PAS EXISTE que vous devriez lire pour voir si d'autres approches fonctionnent mieux et être conscient du potentiel problèmes 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

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!

0
Hayder Nahee

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 ceci.

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.

0
Artem Machnev