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:
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
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!
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.