Existe-t-il une différence de performances entre CTE, sous-requête, table temporaire ou variable de table?

222

Dans cette excellente question SO , les différences entre CTEet sub-queriesont été discutées.

Je voudrais demander spécifiquement:

Dans quelles circonstances chacun des éléments suivants est-il plus efficace / plus rapide?

  • CTE
  • Sous-requête
  • Table temporaire
  • Variable de table

Traditionnellement, j'en ai utilisé beaucoup temp tablesdans le développement stored procedures- car ils semblent plus lisibles que beaucoup de sous-requêtes entrelacées.

Non-recursive CTEs encapsulent très bien les ensembles de données et sont très lisibles, mais y a-t-il des circonstances spécifiques où l'on peut dire qu'ils fonctionneront toujours mieux? ou s'agit-il de devoir toujours tripoter les différentes options pour trouver la solution la plus efficace?


ÉDITER

On m'a récemment dit qu'en termes d'efficacité, les tables temporaires sont un bon premier choix car elles ont un histogramme associé, c'est-à-dire des statistiques.

pourquoi
la source
4
Réponse générale: cela dépend. Et cela dépend de nombreux facteurs, toute déclaration générale est probablement fausse - dans certaines situations. Fondamentalement: vous devez tester et mesurer - voyez ce qui vous convient le mieux!
marc_s
@marc_s - ok; peut-être que cette question devrait être fermée pour être subjective? Remarquez que de nombreuses questions SQL sur SO peuvent être considérées comme subjectives.
whytheq
1
Il pourrait être considéré comme trop large - et je suis d'accord avec vous - beaucoup de choses et de sujets dans SQL obtiendront vraiment une réponse, cela dépend . Parfois, on peut énumérer deux ou trois critères pour prendre une décision, mais avec votre question ici, il est presque impossible de donner des conseils judicieux - cela dépend de tant de choses - vos structures de table, les données de ces tables, les requêtes que vous utilisez, votre stratégie d'indexation et bien plus encore ....
marc_s
@marc_s ce serait bien d'essayer de garder - des conseils sur les modifications possibles à OP pour essayer de le rendre plus précis et plus étroit?
whytheq
Veuillez noter que cette question est spécifique à SQL Server. Pour d'autres bases de données comme les postgres, un CTE est souvent beaucoup plus lent que les sous-requêtes équivalentes (voir http://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/ )
Jay

Réponses:

243

SQL est un langage déclaratif, pas un langage procédural. Autrement dit, vous construisez une instruction SQL pour décrire les résultats souhaités. Vous ne dites pas au moteur SQL comment faire le travail.

En règle générale, il est judicieux de laisser le moteur SQL et l'optimiseur SQL trouver le meilleur plan de requête. Le développement d'un moteur SQL demande de nombreuses années-personnes, alors laissez les ingénieurs faire ce qu'ils savent faire.

Bien sûr, il existe des situations où le plan de requête n'est pas optimal. Ensuite, vous souhaitez utiliser des conseils de requête, restructurer la requête, mettre à jour les statistiques, utiliser des tables temporaires, ajouter des index, etc. pour obtenir de meilleures performances.

Quant à votre question. Les performances des CTE et des sous-requêtes devraient, en théorie, être les mêmes, car les deux fournissent les mêmes informations à l'optimiseur de requêtes. Une différence est qu'un CTE utilisé plus d'une fois peut être facilement identifié et calculé une fois. Les résultats pourraient ensuite être stockés et lus plusieurs fois. Malheureusement, SQL Server ne semble pas tirer parti de cette méthode d'optimisation de base (vous pourriez appeler cette élimination de sous-requête commune).

Les tables temporaires sont différentes, car vous fournissez plus de conseils sur la façon dont la requête doit être exécutée. Une différence majeure est que l'optimiseur peut utiliser les statistiques de la table temporaire pour établir son plan de requête. Cela peut entraîner des gains de performances. De plus, si vous avez un CTE (sous-requête) compliqué qui est utilisé plusieurs fois, le stockage dans une table temporaire augmentera souvent les performances. La requête n'est exécutée qu'une seule fois.

La réponse à votre question est que vous devez jouer pour obtenir les performances que vous attendez, en particulier pour les requêtes complexes qui sont exécutées régulièrement. Dans un monde idéal, l'optimiseur de requêtes trouverait le chemin d'exécution parfait. Bien qu'il le fasse souvent, vous pourrez peut-être trouver un moyen d'obtenir de meilleures performances.

Gordon Linoff
la source
11
Certaines recherches de Microsoft sur d'éventuelles améliorations futures dans ce domaine figurent dans la publication «Exploitation efficace de sous-expressions similaires pour le traitement des requêtes». Disponible ici
Martin Smith
3
Étant donné que ce document a été présenté en 2007, avez-vous une idée s'ils l'ont incorporé dans SQL Server 2012?
Gordon Linoff
3
Une excellente réponse! Juste pour souligner: SQL est un langage déclaratif, et nous ne contrôlons pas COMMENT les données sont extraites. Par conséquent, les performances / vitesse varient d'une requête à l'autre.
Simcha Khabinsky
2
@RGS. . . Les index sur les tables temporaires améliorent définitivement les requêtes qui peuvent tirer parti de ces index - comme avec les index sur une table permanente. Mais, si vous matérialisez une sous-requête en tant que table temporaire, vous risquez de perdre l'avantage des index sur les tables d'origine.
Gordon Linoff
2
@RGS. . Lorsqu'un moteur de base de données matérialise une sous-requête / CTE au cours de l'exécution d'une requête complexe, il n'ajoute pas d'index sur la matérialisation. Vous pouvez le faire manuellement à l'aide de tables temporaires.
Gordon Linoff
77

Il n'y a pas de règle. Je trouve les CTE plus lisibles et les utilise à moins qu'ils ne présentent un problème de performance, auquel cas j'étudie le problème réel plutôt que de deviner que le CTE est le problème et j'essaie de le réécrire en utilisant une approche différente. Le problème ne se résume généralement pas à la façon dont j'ai choisi de déclarer mes intentions de manière déclarative avec la requête.

Il y a certainement des cas où vous pouvez démêler des CTE ou supprimer des sous-requêtes et les remplacer par une table #temp et réduire la durée. Cela peut être dû à diverses choses, telles que les statistiques obsolètes, l'impossibilité même d'obtenir des statistiques précises (par exemple, se joindre à une fonction de valeur de table), le parallélisme ou même l'incapacité à générer un plan optimal en raison de la complexité de la requête ( dans ce cas, sa rupture peut donner à l'optimiseur une chance de se battre). Mais il existe également des cas où les E / S impliquées dans la création d'une table #temp peuvent l'emporter sur les autres aspects des performances qui peuvent rendre une forme de plan particulière utilisant un CTE moins attrayante.

Honnêtement, il y a beaucoup trop de variables pour fournir une réponse «correcte» à votre question. Il n'y a aucun moyen prévisible de savoir quand une requête peut pencher en faveur d'une approche ou d'une autre - sachez simplement qu'en théorie, la même sémantique pour un CTE ou une seule sous-requête devrait exécuter exactement la même chose. Je pense que votre question serait plus utile si vous présentez des cas où ce n'est pas vrai - il se peut que vous ayez découvert une limitation dans l'optimiseur (ou en ayez découvert une connue), ou il se peut que vos requêtes ne soient pas sémantiquement équivalentes ou que celui-ci contient un élément qui contrarie l'optimisation.

Je suggère donc d'écrire la requête de la manière qui vous semble la plus naturelle et de ne dévier que lorsque vous découvrez un problème de performances réel que l'optimiseur rencontre. Personnellement, je les classe en CTE, puis en sous-requête, la table #temp étant le dernier recours.

Aaron Bertrand
la source
4
+1 se révélant être une question assez subjective; J'espère qu'il ne sera pas fermé pour être trop vague car les réponses jusqu'à présent sont informatives. Je me rends compte :-) vous n'aimez pas quand les questions changent, mais avez-vous des suggestions pour réduire la question dans le PO?
whytheq
2
Je pense que cette question est très bien, vous remarquerez qu'il n'y a pas encore un seul vote de clôture, mais si les réponses commencent à déferler follement, elles seront probablement fermées. Comme je l'ai suggéré dans ma réponse, si vous avez un cas particulier où vous voyez une grande différence entre un CTE et une sous-requête, commencez une nouvelle question avec les requêtes et les plans d'exécution réels (et cela pourrait être mieux adapté à dba.se ) . Sachez simplement que la réponse pour aider avec cette requête peut ne pas être la même réponse pour une requête différente avec le même scénario.
Aaron Bertrand
Sous votre question, il y a des liens link / edit / close / flag- s'il y a eu des votes pour fermer la question, vous verrez close (n)nreprésente le nombre d'utilisateurs qui ont voté pour fermer votre question. Si vous cliquez sur le lien, vous verrez les raisons pour lesquelles ces utilisateurs ont sélectionné.
Aaron Bertrand
@whytheq consultez également ce récent article de blog de Bob Beauchemin . Il ne traite pas spécifiquement CTE vs sous-requête, mais le même type de concept s'applique: si vous choisissez un modèle non intuitif pour des raisons de performances, documentez-le et re-visitez-le pour vous assurer que le caprice que vous avez découvert est toujours réel. Je pourrais même suggérer de laisser la version la plus naturelle de la requête commentée, à moins que vous n'ayez un système de contrôle de source fiable en place qui contient la version précédente.
Aaron Bertrand
1
Lien fixe ci-dessus: sqlskills.com/blogs/bobb/…
ADJenks
19

#temp est matérialisé et CTE ne l'est pas.

CTE est juste une syntaxe donc en théorie c'est juste une sous-requête. Il est exécuté. #temp est matérialisé. Ainsi, un CTE coûteux dans une jointure exécutée plusieurs fois peut être préférable dans un #temp. D'un autre côté, si c'est une évaluation facile qui n'est pas exécutée mais quelques fois alors ne vaut pas la surcharge de #temp.

Il y a des gens sur SO qui n'aiment pas les variables de table mais je les aime car ils sont matérialisés et plus rapides à créer que #temp. Il y a des moments où l'optimiseur de requête fait mieux avec un #temp par rapport à une variable de table.

La possibilité de créer un PK sur une variable #temp ou table donne à l'optimiseur de requête plus d'informations qu'un CTE (car vous ne pouvez pas déclarer un PK sur un CTE).

paparazzo
la source
quel est l'acronyme "TVP" ... quelque chose de similaire à #temp?
whytheq
TVP devient un terme courant, car il semble impressionnant (pour certains). En bref, un TVP est une table passée en paramètre. Quiconque a utilisé des variables de tableau sera à l'aise avec elles.
WonderWorker
1
AVERTISSEMENT - Les TVP n'ont pas de plans d'exécution! N'utilisez pas de TVP pour autre chose, la plus simple des listes de recherche courtes. Si vous effectuez des jointures, des insertions ou des mises à jour complexes, vous pouvez rencontrer des problèmes d'optimisation massifs. Croyez-moi, j'ai été brûlé par ça.
Heliac
12

Je pense que seulement 2 choses qui rendent TOUJOURS préférable d'utiliser une table # Temp plutôt qu'un CTE sont:

  1. Vous ne pouvez pas mettre de clé primaire sur un CTE, de sorte que les données auxquelles le CTE accède devront traverser chacun des index des tables du CTE plutôt que d'accéder simplement au PK ou à l'index sur la table temporaire.

  2. Parce que vous ne pouvez pas ajouter de contraintes, d'index et de clés primaires à un CTE, ils sont plus sujets aux bogues et aux mauvaises données.


-onjour quand hier

Voici un exemple où les contraintes #table peuvent empêcher les mauvaises données ce qui n'est pas le cas dans les CTE

DECLARE @BadData TABLE ( 
                       ThisID int
                     , ThatID int );
INSERT INTO @BadData
       ( ThisID
       , ThatID
       ) 
VALUES
       ( 1, 1 ),
       ( 1, 2 ),
       ( 2, 2 ),
       ( 1, 1 );

IF OBJECT_ID('tempdb..#This') IS NOT NULL
    DROP TABLE #This;
CREATE TABLE #This ( 
             ThisID int NOT NULL
           , ThatID int NOT NULL
                        UNIQUE(ThisID, ThatID) );
INSERT INTO #This
SELECT * FROM @BadData;
WITH This_CTE
     AS (SELECT *
           FROM @BadData)
     SELECT *
       FROM This_CTE;
ShanksPranks
la source
3
ALWAYSest un peu trop loin mais merci pour la réponse. En termes de lisibilité, l'utilisation des CTE peut être une bonne chose.
whytheq
3
Je ne comprends pas du tout votre deuxième point. À mon avis, la requête définissant le CTE est analogue aux contraintes que vous mettriez sur la table temporaire, notant que les premières peuvent comprendre des prédicats arbitrairement complexes tandis que les secondes sont beaucoup plus limitées (par exemple, la CHECKcontrainte faisant référence à plusieurs lignes / tables est interdit). Pouvez-vous publier un exemple où un CTE présente un bogue que l'équivalent de la table temporaire ne fait pas?
onedaywhen