Esiste una differenza di prestazioni tra CTE, sottoquery, tabella temporanea o variabile tabella?


222

In questa eccellente domanda SO , sono state discusse le differenze tra CTEe sub-queries.

Vorrei chiedere specificamente:

In quale circostanza ciascuna delle seguenti è più efficiente / più veloce?

  • CTE
  • Sotto-Query
  • Tabella temporanea
  • Variabile di tabella

Tradizionalmente, ho usato molte cose temp tablesin fase di sviluppo stored procedures, in quanto sembrano più leggibili di molte query secondarie intrecciate.

Non-recursive CTEs incapsulano molto bene insiemi di dati e sono molto leggibili, ma ci sono circostanze specifiche in cui si può dire che funzioneranno sempre meglio? o si tratta di dover sempre giocherellare con le diverse opzioni per trovare la soluzione più efficiente?


MODIFICARE

Recentemente mi è stato detto che in termini di efficienza, le tabelle temporanee sono una buona prima scelta in quanto hanno un istogramma associato, cioè le statistiche.


4
Risposta generale: dipende. E dipende da molti fattori, ogni affermazione generale è probabilmente falsa - in alcune situazioni. Fondamentalmente: devi testare e misurare - vedi quale funziona meglio per te!
marc_s,

@marc_s - ok; forse questa domanda dovrebbe essere chiusa per essere soggettiva? Intendiamoci, molte domande SQL su SO potrebbero essere giudicate soggettive.
whytheq,

1
Potrebbe essere chiuso perché troppo ampio - e sono d'accordo con te - molte cose e argomenti in SQL otterranno davvero una risposta , dipende . A volte uno può elencare due o tre criteri per prendere una decisione, ma con la tua domanda qui, è quasi impossibile dare un buon consiglio - dipende da così tanto - le strutture della tabella, i dati in quelle tabelle, le query che stai utilizzando, la tua strategia di indicizzazione e molto altro ancora ....
marc_s

@marc_s sarebbe bello provare e mantenere - qualche consiglio su possibili modifiche a OP per cercare di renderlo più specifico e ristretto?
whytheq,

Questa domanda è specifica per SQL Server. Per altri DB come Postgres, un CTE è spesso molto più lento delle equivalenti subquery (vedi http://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/ )
Jay,

Risposte:


243

SQL è un linguaggio dichiarativo, non un linguaggio procedurale. Cioè, si costruisce un'istruzione SQL per descrivere i risultati desiderati. Non stai dicendo al motore SQL come fare il lavoro.

Come regola generale, è una buona idea lasciare che il motore SQL e l'ottimizzatore SQL trovino il miglior piano di query. Ci sono molti anni di lavoro personale che vanno allo sviluppo di un motore SQL, quindi lascia che gli ingegneri facciano ciò che sanno fare.

Naturalmente, ci sono situazioni in cui il piano di query non è ottimale. Quindi si desidera utilizzare i suggerimenti per le query, ristrutturare la query, aggiornare le statistiche, utilizzare tabelle temporanee, aggiungere indici e così via per ottenere prestazioni migliori.

Per quanto riguarda la tua domanda. Le prestazioni di CTE e sottoquery dovrebbero, in teoria, essere le stesse poiché entrambe forniscono le stesse informazioni a Query Optimizer. Una differenza è che un CTE usato più di una volta potrebbe essere facilmente identificato e calcolato una volta. I risultati potrebbero quindi essere memorizzati e letti più volte. Sfortunatamente, SQL Server non sembra trarre vantaggio da questo metodo di ottimizzazione di base (è possibile chiamare questa eliminazione di subquery comune).

Le tabelle temporanee sono una questione diversa, perché stai fornendo ulteriori indicazioni su come eseguire la query. Una delle principali differenze è che l'ottimizzatore può utilizzare le statistiche della tabella temporanea per stabilire il suo piano di query. Ciò può comportare miglioramenti delle prestazioni. Inoltre, se si dispone di un CTE (sottoquery) complicato che viene utilizzato più di una volta, la sua memorizzazione in una tabella temporanea spesso migliorerà le prestazioni. La query viene eseguita una sola volta.

La risposta alla tua domanda è che devi giocare per ottenere le prestazioni che ti aspetti, in particolare per query complesse che vengono eseguite su base regolare. In un mondo ideale, Query Optimizer trova il percorso di esecuzione perfetto. Anche se spesso accade, potresti essere in grado di trovare un modo per ottenere prestazioni migliori.


11
Alcune ricerche Microsoft su possibili futuri miglioramenti in questo settore sono nella pubblicazione "Sfruttamento efficiente di sottoespressioni simili per l'elaborazione delle query" Disponibile da qui
Martin Smith,

3
Dato che quel documento è stato presentato nel 2007, hai idea di averlo incorporato in SQL Server 2012?
Gordon Linoff,

3
Un'ottima risposta! Solo per sottolineare: SQL è un linguaggio dichiarativo e non controlliamo come vengono estratti i dati. Pertanto, le prestazioni / velocità variano da query a query.
Simcha Khabinsky,

2
@RGS. . . Gli indici su tabelle temporanee migliorano sicuramente le query che possono trarre vantaggio da tali indici, come con gli indici su una tabella permanente. Tuttavia, se materializzi una sottoquery come tabella temporanea, potresti perdere il vantaggio degli indici sulle tabelle originali.
Gordon Linoff,

2
@RGS. . Quando un motore di database materializza una sottoquery / CTE nel corso dell'esecuzione di una query complessa, non aggiunge indici sulla materializzazione. Puoi farlo manualmente usando le tabelle temporanee.
Gordon Linoff,

77

Non c'è una regola. Trovo i CTE più leggibili e li uso a meno che non presentino qualche problema di prestazioni, nel qual caso indago il problema reale piuttosto che indovinare che il CTE è il problema e provo a riscriverlo usando un approccio diverso. Di solito c'è un problema in più rispetto al modo in cui ho scelto di dichiarare dichiaratamente le mie intenzioni con la query.

Ci sono certamente casi in cui è possibile svelare CTE o rimuovere sottoquery e sostituirle con una tabella #temp e ridurre la durata. Ciò può essere dovuto a varie cose, come le statistiche obsolete, l'incapacità di ottenere statistiche accurate (ad esempio, unirsi a una funzione con valori di tabella), il parallelismo o persino l'incapacità di generare un piano ottimale a causa della complessità della query ( nel qual caso la sua rottura può dare all'ottimizzatore una possibilità di combattimento). Ma ci sono anche casi in cui l'I / O coinvolto nella creazione di una tabella #temp può superare gli altri aspetti delle prestazioni che possono rendere una particolare forma del piano usando un CTE meno attraente.

Onestamente, ci sono troppe variabili per fornire una risposta "corretta" alla tua domanda. Non esiste un modo prevedibile per sapere quando una query può dare un suggerimento a favore di un approccio o di un altro: basta sapere che, in teoria, la stessa semantica per un CTE o una singola sottoquery dovrebbe eseguire esattamente lo stesso. Penso che la tua domanda sarebbe più preziosa se presenti alcuni casi in cui ciò non è vero: è possibile che tu abbia scoperto una limitazione nell'ottimizzatore (o ne abbia scoperto uno noto) o che le tue query non siano semanticamente equivalenti o quello contiene un elemento che ostacola l'ottimizzazione.

Quindi suggerirei di scrivere la query in un modo che ti sembra più naturale e deviare solo quando scopri un reale problema di prestazioni dell'ottimizzatore. Personalmente li classifico CTE, quindi subquery, con la tabella #temp come ultima risorsa.


4
+1 che si rivela essere una domanda piuttosto soggettiva; Spero che non venga chiuso per essere troppo vago in quanto le risposte finora sono informative. Mi rendo conto :-) Non ti piace quando le domande cambiano ma hai qualche suggerimento per restringere la domanda nel PO?
whytheq,

2
Penso che questa domanda vada bene, noterai che non esiste ancora un solo voto di chiusura, ma se le risposte iniziano a muoversi selvaggiamente, probabilmente si chiuderà. Come ho suggerito nella mia risposta, se hai un caso particolare in cui vedi una grande differenza tra un CTE e una sottoquery, inizia una nuova domanda con le query e i piani di esecuzione effettivi (e potrebbe adattarsi meglio a dba.se ) . Basta rendersi conto che la risposta per aiutare con quella query potrebbe non essere la stessa risposta per una query diversa con lo stesso scenario.
Aaron Bertrand,

Proprio sotto la tua domanda ci sono collegamenti link / edit / close / flag- se ci sono stati voti per chiudere la domanda, vedrai close (n)dove nrappresenta il numero di utenti che hanno votato per chiudere la tua domanda. Se fai clic sul link vedrai i motivi per cui quegli utenti sono stati selezionati.
Aaron Bertrand,

@whytheq vede anche questo recente post sul blog di Bob Beauchemin . Non tratta in modo specifico CTE vs. subquery ma si applica lo stesso tipo di concetto: se si sceglie un modello non intuitivo per motivi di prestazioni, documentare la schifezza da esso e visitarlo nuovamente per assicurarsi che la stranezza che hai scoperto sia ancora reale. Potrei anche suggerire di lasciare la versione più naturale della query commentata, a meno che non si disponga di un sistema di controllo del codice sorgente affidabile che contenga la versione precedente.
Aaron Bertrand,

1
Collegamento fisso sopra: sqlskills.com/blogs/bobb/…
ADJenks

19

#temp è materalizzato e CTE no.

CTE è solo una sintassi, quindi in teoria è solo una sottoquery. Viene eseguito. #temp è materializzato. Quindi un CTE costoso in un join eseguito più volte potrebbe essere migliore in un #temp. D'altra parte, se si tratta di una valutazione semplice che non viene eseguita, ma alcune volte, non vale il sovraccarico di #temp.

Ci sono alcune persone su SO che non amano la variabile table ma mi piacciono poiché sono materializzate e più veloci da creare rispetto a #temp. Ci sono momenti in cui Query Optimizer funziona meglio con un #temp rispetto a una variabile di tabella.

La possibilità di creare un PK su una variabile #temp o table fornisce all'ottimizzatore di query più informazioni di un CTE (poiché non è possibile dichiarare un PK su un CTE).


qual è la sigla "TVP" ... qualcosa di simile a #temp?
whytheq

TVP sta diventando un termine comune, perché sembra impressionante (per alcuni). In breve, un TVP è una tabella passata come parametro. Chiunque abbia usato le variabili di tabella sarà a casa con loro.
WonderWorker,

1
ATTENZIONE - I TVP non hanno piani di esecuzione! Non utilizzare i TVP per qualcos'altro, il più semplice dei brevi elenchi di ricerca. Se si eseguono join, inserimenti o aggiornamenti complessi, è possibile che si verifichino enormi problemi di ottimizzazione. Fidati di me, sono stato bruciato da questo.
Heliac,

12

Solo 2 cose che penso rendono SEMPRE preferibile usare una tabella # Temp piuttosto che un CTE sono:

  1. Non è possibile inserire una chiave primaria in un CTE, pertanto i dati a cui accede il CTE dovranno attraversare ciascuno degli indici nelle tabelle del CTE anziché accedere al PK o all'indice nella tabella temporanea.

  2. Poiché non è possibile aggiungere vincoli, indici e chiavi primarie a un CTE, sono più inclini a bug che si insinuano e dati errati.


-domani ieri

Ecco un esempio in cui i vincoli #table possono prevenire dati errati che non è il caso di 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;

3
ALWAYSè un po 'troppo lontano ma grazie per la risposta. In termini di leggibilità, l'uso dei CTE può essere una buona cosa.
whytheq,

3
Non capisco affatto il tuo secondo punto. Per come la vedo io, la query che definisce il CTE è analoga ai vincoli che metteresti sulla tabella temporanea, osservando che il primo può comprendere predicati arbitrariamente complessi mentre il secondo è molto più limitato (ad esempio il CHECKvincolo che fa riferimento a più righe / tabelle è non autorizzato). Puoi pubblicare un esempio in cui un CTE mostra un bug che non è l'equivalente della tabella temporanea?
giorno
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.