SI EXISTE prend plus de temps que l'instruction select intégrée

35

Lorsque je lance le code suivant, cela prend 22,5 minutes et 106 millions de lectures. Cependant, si je lance uniquement l'instruction select interne en elle-même, cela ne prend que 15 secondes et 264k lectures. En remarque, la requête sélectionnée ne renvoie aucun enregistrement.

Avez-vous une idée de la raison pour laquelle le programme IF EXISTSdurerait beaucoup plus longtemps et ferait beaucoup plus de lectures? J'ai également changé l'instruction select à faire SELECT TOP 1 [dlc].[id]et je l'ai tuée après 2 minutes.

En guise de solution temporaire, je l’ai modifiée pour qu’elle compte (*) et affecte cette valeur à une variable @cnt. Ensuite, il fait une IF 0 <> @cntdéclaration. Mais je pensais que ce EXISTSserait mieux, car si des enregistrements étaient renvoyés dans l'instruction select, ils ne procéderaient plus à l'analyse / à la recherche une fois qu'ils auraient trouvé au moins un enregistrement, alors que la count(*)requête complète serait complétée. Qu'est-ce que je rate?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END
Chris Woods
la source
4
Pour éviter le problème de l’objectif de la ligne, une autre idée (non testée, n’oubliez pas!) Pourrait être d’essayer l’inverse - IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END.
Aaron Bertrand

Réponses:

32

Avez-vous une idée de la raison pour laquelle le programme IF EXISTSdurerait beaucoup plus longtemps et ferait beaucoup plus de lectures? J'ai également changé l'instruction select à faire SELECT TOP 1 [dlc].[id]et je l'ai tuée après 2 minutes.

Comme je l'ai expliqué dans ma réponse à cette question connexe:

Comment (et pourquoi) TOP impacte-t-il un plan d'exécution?

L'utilisation EXISTSintroduit un objectif de ligne, dans lequel l'optimiseur génère un plan d'exécution visant à localiser rapidement la première ligne. Ce faisant, il suppose que les données sont uniformément distribuées. Par exemple, si les statistiques indiquent qu'il y a 100 correspondances attendues sur 100 000 lignes, cela suppose qu'il ne faudra lire que 1 000 lignes pour trouver la première correspondance.

Cela se traduira par des temps d'exécution plus longs que prévu si cette hypothèse s'avère erronée. Par exemple, si SQL Server choisit une méthode d'accès (par exemple, une analyse non ordonnée) permettant de localiser la première valeur correspondante très tard dans la recherche, une analyse presque complète pourrait en résulter. D'autre part, si une ligne correspondante se trouve parmi les premières lignes, les performances seront très bonnes. C'est le risque fondamental avec les objectifs de rangée - une performance incohérente.

En guise de solution temporaire, je l’ai modifiée pour qu’elle compte (*) et affecte cette valeur à une variable.

Il est généralement possible de reformuler la requête de manière à ce qu'aucun objectif de ligne ne soit attribué. Sans l'objectif de ligne, la requête peut toujours se terminer lorsque la première ligne correspondante est rencontrée (si elle est écrite correctement), mais la stratégie de plan d'exécution risque d'être différente (et, espérons-le, plus efficace). De toute évidence, compter (*) nécessitera la lecture de toutes les lignes. Ce n'est donc pas une alternative parfaite.

Si vous exécutez SQL Server 2008 R2 ou une version ultérieure, vous pouvez également utiliser généralement l' indicateur de suivi documenté et pris en charge 4138 pour obtenir un plan d'exécution sans objectif de ligne. Cet indicateur peut également être spécifié à l'aide de l' indicateur pris en charge. Sachez OPTION (QUERYTRACEON 4138) toutefois qu'il nécessite une autorisation sysadmin d' exécution , à moins qu'il ne soit utilisé avec un repère de plan.

Malheureusement

Aucune de ces réponses n'est fonctionnelle avec une IF EXISTSinstruction conditionnelle. Cela s'applique uniquement aux DML ordinaires. Il va travailler avec l'autre SELECT TOP (1)formulation que vous avez essayé. Cela peut être mieux que d’utiliser COUNT(*), qui doit compter toutes les lignes qualifiées, comme mentionné précédemment.

Cela dit, il existe un certain nombre de façons d’exprimer cette exigence qui vous permettront d’éviter ou de contrôler l’objectif de la ligne, tout en mettant fin à la recherche plus tôt. Un dernier exemple:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;
Paul White dit GoFundMonica
la source
L'exemple alt que vous avez fourni a duré 3,75 minutes et effectué 46 millions de lectures. Donc, bien que plus rapide que ma requête initiale, je pense que dans ce cas, je vais m'en tenir à @cnt = count (*) et évaluer la variable par la suite. Surtout que cela fonctionne 99% du temps, il n'y aura rien dedans. D'après vos réponses et celles de Rob, il semble que Exists ne fonctionne bien que si vous attendez vraiment un résultat quelconque et que ce résultat soit distribué de manière égale dans vos données.
Chris Woods
3
@ChrisWoods: Vous avez dit "Surtout que cela fonctionne 99% du temps, il n'y aura rien dedans". Cela garantit à peu près que l'objectif d'une ligne est une mauvaise idée, car vous vous attendez à ce qu'il n'y ait généralement PAS de lignes et que vous ayez à tout analyser pour vous assurer qu'il n'y en a pas. Si vous ne pouvez pas ajouter d’index astucieux, utilisez COUNT (*).
Ross Presser le
25

Comme EXISTS n'a besoin que de trouver une seule ligne, il utilisera un objectif de ligne. Cela peut parfois produire un plan moins qu'idéal. Si vous vous attendez à ce que ce soit le cas, renseignez une variable avec le résultat de a COUNT(*), puis testez-la pour voir si elle est supérieure à 0.

Alors ... Avec un objectif de petite ligne, cela évitera les opérations de blocage, telles que la construction de tables de hachage, ou le tri des flux qui pourraient être utiles pour la fusion des jointures, car il est évident que la recherche de quelque chose est voué à être rapide, et que, par conséquent, les boucles imbriquées être le meilleur si il a trouvé quelque chose. Sauf que cela peut rendre un plan qui est bien pire dans l'ensemble. Si trouver une seule ligne a été rapide, vous aimeriez cette méthode pour éviter les blocs ...

Rob Farley
la source