2009-05-28 10 views
10

Ho una domanda che sembraPerché l'inserimento e il join delle tabelle #temp è più veloce?

SELECT 
P.Column1, 
P.Column2, 
P.Column3, 
... 
(
    SELECT 
     A.ColumnX, 
     A.ColumnY, 
     ... 
    FROM 
     dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A 
    WHERE 
     A.Key = P.Key 
    FOR XML AUTO, TYPE 
), 
(
    SELECT 
     B.ColumnX, 
     B.ColumnY, 
     ... 
    FROM 
     dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B 
    WHERE 
     B.Key = P.Key 
    FOR XML AUTO, TYPE 
) 
FROM 
(
    <joined tables here> 
) AS P 
FOR XML AUTO,ROOT('ROOT') 

P ha ~ 5000 righe A e B ~ 4000 righe ciascuna

Questa query ha un tempo di esecuzione di prestazioni ~ 10 + minuti.

La modifica a questo però:

SELECT 
P.Column1, 
P.Column2, 
P.Column3, 
... 
INTO #P 

SELECT 
A.ColumnX, 
A.ColumnY, 
... 
INTO #A  
FROM 
dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A 

SELECT 
B.ColumnX, 
B.ColumnY, 
... 
INTO #B  
FROM 
dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B 


SELECT 
P.Column1, 
P.Column2, 
P.Column3, 
... 
(
    SELECT 
     A.ColumnX, 
     A.ColumnY, 
     ... 
    FROM 
     #A AS A 
    WHERE 
     A.Key = P.Key 
    FOR XML AUTO, TYPE 
), 
(
    SELECT 
     B.ColumnX, 
     B.ColumnY, 
     ... 
    FROM 
     #B AS B 
    WHERE 
     B.Key = P.Key 
    FOR XML AUTO, TYPE 
) 
FROM #P AS P 
FOR XML AUTO,ROOT('ROOT')  

ha una prestazione di ~ 4 secondi.

Questo non ha molto senso, come sembrerebbe il costo da inserire in una tabella temporanea e quindi il join dovrebbe essere più alto di default. La mia inclinazione è che SQL stia facendo il tipo sbagliato di "join" con la subquery, ma forse l'ho perso, non c'è modo di specificare il tipo di join da usare con sottoquery correlate.

C'è un modo per ottenere ciò senza utilizzare le tabelle #temp/@ variabili di tabella tramite indici e/o suggerimenti?

MODIFICA: si noti che dbo.TableReturningFunc1 e dbo.TableReturningFunc2 sono in linea TVF, non multi-statement o sono istruzioni di visualizzazione "parametrizzate".

risposta

15

Le tue procedure vengono rivalutate per ogni riga in P.

Quello che si fa con le tabelle temporanee è infatti il ​​caching del set di risultati generato dalle stored procedure, eliminando così la necessità di rivalutare.

L'inserimento in una tabella temporanea è veloce perché non genera redo/rollback.

Unisce sono anche veloce, dal momento che avere un gruppo di risultati stabile permette possibilità di creare un indice temporaneo con una Eager Spool o un Worktable

È possibile riutilizzare le procedure senza tabelle temporanee, utilizzando CTE 's, ma per questo di essere efficiente, SQL Server deve materializzare i risultati di CTE.

Si può provare di forzarlo fare questo con l'utilizzo di un ORDER BY all'interno di una sottoquery:

WITH f1 AS 
     (
     SELECT TOP 1000000000 
       A.ColumnX, 
       A.ColumnY 
     FROM dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A 
     ORDER BY 
       A.key 
     ), 
     f2 AS 
     (
     SELECT TOP 1000000000 
       B.ColumnX, 
       B.ColumnY, 
     FROM dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B 
     ORDER BY 
       B.Key 
     ) 
SELECT … 

, che può provocare Eager Spool generato dal ottimizzatore.

Tuttavia, questo è lungi dall'essere garantito.

Il modo garantito è aggiungere un OPTION (USE PLAN) alla query e avvolgere il corrispondente CTE nella clausola Spool.

Vai a questa voce nel mio blog su come farlo:

Questo è difficile da mantenere, dal momento che è necessario riscrivere il programma ogni volta che si riscrivere la query, ma questo funziona bene ed è abbastanza efficiente.

L'utilizzo delle tabelle temporanee sarà tuttavia molto più semplice.

+0

Come posso ottenere questo risultato senza fare in modo esplicito la tabella temporanea, se possibile? –

+0

Certo che è possibile, vedere l'aggiornamento post – Quassnoi

+0

Prestazioni con CTE 1:28, metodo tabella temporanea 0:04. Molto meglio di 10+ minuti ... ma ancora ordini di grandezza in differenza. –

2

È un problema con la query secondaria che fa riferimento alla query esterna, ovvero la query secondaria deve essere compilata ed eseguita per ogni riga nella query esterna. Invece di utilizzare tabelle temporanee esplicite, è possibile utilizzare una tabella derivata. Per semplificare il tuo esempio:

SELECT P.Column1, 
     (SELECT [your XML transformation etc] FROM A where A.ID = P.ID) AS A 

Se P contiene 10.000 record quindi selezionare A.ColumnX DA UN dove A.ID = P.ID verrà eseguito 10.000 volte.
si può invece utilizzare una tabella derivata come così:

SELECT P.Column1, A2.Column FROM 
P LEFT JOIN 
(SELECT A.ID, [your XML transformation etc] FROM A) AS A2 
ON P.ID = A2.ID 

Va bene, non che illustrative pseudo-codice, ma l'idea di base è la stessa della tabella temporanea, se non che SQL Server fa il tutto in memoria : Seleziona prima tutti i dati in "A2" e crea una tabella temporanea in memoria, quindi si unisce a essa. Questo ti risparmia di doverlo selezionare a TEMP te stesso.

Solo per darti un esempio del principio in un altro contesto in cui potrebbe avere un senso più immediato. Considerare le informazioni sui dipendenti e sulle assenze in cui si desidera mostrare il numero di giorni di assenza registrati per ciascun dipendente.

Bad: (viene eseguito come molti queryes in quanto vi sono impiegati nel DB)

SELECT EmpName, 
(SELECT SUM(absdays) FROM Absence where Absence.PerID = Employee.PerID) AS Abstotal   
FROM Employee 

Buono: (gira solo due interrogazioni)

SELECT EmpName, AbsSummary.Abstotal 
FROM Employee LEFT JOIN 
     (SELECT PerID, SUM(absdays) As Abstotal 
     FROM Absence GROUP BY PerID) AS AbsSummary 
ON AbsSummary.PerID = Employee.PerID 
+0

Questo non risolve completamente il problema che sto avendo. Usando LEFT OUTER JOINS e convertendo in usando FOR XML PATH (per ottenere il nesting corretto) ottengo un runtime di 1:26. Rispetto a 0:04 per il metodo tabella temporanea. –

+0

Ah, abbastanza giusto :) – Frans

+0

Esatto, ecco cosa farei a questo punto; Suddividi la query nei suoi componenti e carica ogni parte in Query Analyzer e seleziona "mostra piano di esecuzione stimato". Quindi fai la stessa cosa per l'intera query. Dovrebbe darti una buona idea su quale passo è lento e su come l'ottimizzatore sta interpretando la tua richiesta. Vedi se è uno dei sottocomponenti o solo quando è tutto messo insieme. Cerca loop e scansioni. Soprattutto, probabilmente troverai un passaggio che è responsabile di quasi tutto il tempo di esecuzione - vedi se riesci a ottimizzarlo. – Frans

0

Si consiglia di utilizzare l'WITH common_table_expression costrutto per quello che ora hai come sottoselezioni o tabelle temporanee, vedi http://msdn.microsoft.com/en-us/library/ms175972(SQL.90).aspx.

+0

-1 Le espressioni di tabella comuni sono solo viste in linea, non si materializzano in una sottoquery. –

0

Questo non fa un sacco di senso, in quanto sembrerebbe il costo per inserire in una tabella temp e poi fare il join dovrebbe essere più alto da de> Questo non fa un sacco di senso, in quanto sembrerebbe il costo da inserire in una tabella temporanea e quindi fare il join dovrebbe essere più alto di default.fault.

Con tabelle temporanee, si istruisce esplicitamente Server Sql quale memoria intermedia da utilizzare. Ma se si inserisce tutto in una grande query, Sql Server deciderà autonomamente. La differenza non è poi così grande; alla fine della giornata, viene utilizzata la memoria temporanea, indipendentemente dal fatto che la si specifichi come tabella temporanea o meno.

Nel tuo caso, le tabelle temporanee funzionano più velocemente, quindi perché non seguirle?

1

Ci sono diversi possibili motivi per cui l'utilizzo di tabelle temporanee temporanee potrebbe velocizzare una query, ma il più probabile nel tuo caso è che le funzioni chiamate (ma non elencate), probabilmente sono TVF multistrato e non TVF in linea. I TVF a più attestazioni sono opachi all'ottimizzazione delle loro query di chiamata e quindi l'ottimizzatore non è in grado di stabilire se ci sono delle opportunità per il riutilizzo dei dati o altre ottimizzazioni di riordino dell'operatore logico/fisico. Pertanto, tutto ciò che può fare è rieseguire i TVF ogni volta che la query contenente deve produrre un'altra riga con le colonne XML.

In breve, le TVF multi-statement frustrano l'ottimizzatore.

Le soluzioni usuali, in ordine di (tipico) preferenza sono:

  1. riscrivere l'offendere multiistruzione TVF essere un filtro in linea TVF
  2. in linea il codice funzione nel chiamata query, o
  3. Scarica i dati TVF offensivi in ​​una tabella temporanea. che è quello che hai fatto ...
+0

Sono d'accordo, tranne che le TVF sono viste in linea, fondamentalmente parametrizzate. Aggiungerò questo chiarimento alla domanda. Buon suggerimento in generale però. –

+1

+1 per il consiglio generale che le TVF multi-statement sono opache all'ottimizzatore. La stima della cardinalità è un problema particolare. In generale, qualsiasi funzione con 'BEGIN ... END' nella definizione deve essere evitata :) –

-2

Se le tabelle temporanee risultano più veloci nella tua particolare istanza, dovresti invece usare una variabile di tabella.

C'è un buon articolo qui sulle differenze e le implicazioni sulle prestazioni:

http://www.codeproject.com/KB/database/SQP_performance.aspx

+2

In SQL 2005 e sopra le tabelle temporanee sono le variabili di tabella la maggior parte delle volte quanto più veloci o veloci. Ciò è dovuto al fatto che le variabili di tabella non possono avere statistiche su di esse, quindi al Query Optimizer sembra che abbiano solo una riga (guarda il piano di query). In quanto tali, sono ottimizzati in modo errato e tendono a comportarsi male ogni volta che hanno significativamente più di 10 righe. Questo articolo utilizza test simili per l'articolo di riferimento ma mette mostra cosa succede nel 2005 quando inserisci più righe. – RBarryYoung

+0

Oops, ecco l'articolo: http://www.sql-server-performance.com/articles/ per/temp_tables_vs_variables_p1.aspx – RBarryYoung

+0

Heh. In realtà, sono entrambi lo stesso articolo! – RBarryYoung

4

Questa risposta deve essere letto insieme l'articolo di Quassnoi
http://explainextended.com/2009/05/28/generating-xml-in-subqueries/

Con l'applicazione giudiziosa di CROSS APPLY, è possibile forzare la memorizzazione nella cache o la valutazione della scorciatoia di TVF in linea. Questa query restituisce istantaneamente.

SELECT * 
FROM (
     SELECT (
       SELECT f.num 
       FOR XML PATH('fo'), ELEMENTS ABSENT 
       ) AS x 
     FROM [20090528_tvf].t_integer i 
     cross apply (
      select num 
      from [20090528_tvf].fn_num(9990) f 
      where f.num = i.num 
      ) f 
) q 
--WHERE x IS NOT NULL -- covered by using CROSS apply 
FOR XML AUTO 

Non hai fornito strutture reali, quindi è difficile costruire qualcosa di significativo, ma anche la tecnica dovrebbe applicarsi.

Se si modifica la TVF con istruzioni multiple nell'articolo di Quassnoi su una TVF in linea, il piano diventa ancora più veloce (almeno un ordine di grandezza) e il piano si riduce magicamente a qualcosa che non riesco a capire (è troppo semplice!).

CREATE FUNCTION [20090528_tvf].fn_num(@maxval INT) 
RETURNS TABLE 
AS RETURN 
     SELECT num + @maxval num 
     FROM t_integer 

Statistiche

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 

(10 row(s) affected) 
Table 't_integer'. Scan count 2, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

SQL Server Execution Times: 
    CPU time = 0 ms, elapsed time = 2 ms. 
+1

+1 per il suggerimento di convertire in una funzione in linea. Se ciò non fosse desiderabile per qualche motivo, un'alternativa è aggiungere un "PRIMARY KEY" alla definizione di TVF a più istruzioni. Si può anche riscrivere la query come 'SELECT num FROM [20090528_tvf] .t_integer AS ti INTERSECT SELECT num FROM [20090528_tvf] .fn_num (9990) AS fn FOR XML PATH ('fo'), ROOT ('q');' –

0

ho accettato, tavolo Temp è un buon concetto. Quando il numero di righe aumenta in una tabella un esempio di 40 milioni di righe e voglio aggiornare più colonne su una tabella applicando join con un'altra tabella in quel caso, preferirei sempre utilizzare l'espressione di tabella comune per aggiornare le colonne nell'istruzione select usando case statement, ora il mio set di risultati statement select contiene le righe aggiornate.Inserire 40 milioni di record in una tabella temporanea con istruzione select usando l'istruzione case ha richiesto 21 minuti e quindi creare un indice ha impiegato 10 minuti, quindi il tempo di creazione degli insiemi e dell'indice è stato di 30 minuti . Quindi applicherò l'aggiornamento unendomi al set di risultati aggiornato della tabella temporanea con la tabella principale. Ci sono voluti 5 minuti per aggiornare 10 milioni di record su 40 milioni di record, quindi il mio tempo di aggiornamento complessivo per 10 milioni di record è durato quasi 35 minuti contro 5 minuti dall'espressione della tabella comune. La mia scelta in questo caso è un'espressione di tabella comune.

Problemi correlati