2010-11-20 19 views
21

Ho trovato che nei piani di esecuzione utilizzando spool di sottoespressione comuni che le letture logiche riportate diventano piuttosto elevate per le tabelle di grandi dimensioni.Perché le letture logiche per le funzioni aggregate con finestra sono così elevate?

Dopo alcune prove ed errori, ho trovato una formula che sembra contenere lo script di test e il piano di esecuzione di seguito. Worktable logical reads = 1 + NumberOfRows * 2 + NumberOfGroups * 4

Non capisco il motivo per cui questa formula è valida. È più di quanto avrei pensato fosse necessario osservare il piano. Qualcuno può dare un colpo in base al conto di ciò che sta accadendo che conti per questo?

Oppure, in caso contrario, esiste un modo per tracciare quale pagina è stata letta in ciascuna lettura logica in modo che possa risolverla da sola?

SET STATISTICS IO OFF; SET NOCOUNT ON; 

IF Object_id('tempdb..#Orders') IS NOT NULL 
    DROP TABLE #Orders; 

CREATE TABLE #Orders 
    (
    OrderID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED, 
    CustomerID NCHAR(5) NULL, 
    Freight MONEY NULL, 
); 

CREATE NONCLUSTERED INDEX ix 
    ON #Orders (CustomerID) 
    INCLUDE (Freight); 

INSERT INTO #Orders 
VALUES (N'ALFKI', 29.46), 
     (N'ALFKI', 61.02), 
     (N'ALFKI', 23.94), 
     (N'ANATR', 39.92), 
     (N'ANTON', 22.00); 

SELECT PredictedWorktableLogicalReads = 
     1 + 2 * Count(*) + 4 * Count(DISTINCT CustomerID) 
FROM #Orders; 

SET STATISTICS IO ON; 

SELECT OrderID, 
     Freight, 
     Avg(Freight) OVER (PARTITION BY CustomerID) AS Avg_Freight 
FROM #Orders; 

uscita

PredictedWorktableLogicalReads 
------------------------------ 
23 

Table 'Worktable'. Scan count 3, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table '#Orders___________000000000002'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Execution Plan

Informazione supplementare:

C'è una buona spiegazione di questi spool nel capitolo 3 del libro Query Tuning and Optimization e this blog post by Paul White.

In sintesi l'iteratore di segmento nella parte superiore del piano aggiunge un flag alle righe che invia quando indica l'inizio di una nuova partizione. Lo spool del segmento primario riceve una riga alla volta dall'iteratore del segmento e lo inserisce in una tabella di lavoro in tempdb. Una volta che ottiene la bandiera che dice che un nuovo gruppo ha iniziato, restituisce una riga all'ingresso superiore dell'operatore dei cicli nidificati. Ciò fa sì che l'aggregazione del flusso sia invocata sulle righe nella tabella di lavoro, la media viene calcolata, quindi questo valore viene ricollocato con le righe nella tabella di lavoro prima che la tabella di lavoro sia troncata e pronta per il nuovo gruppo. Lo spool del segmento emette una fila fittizia per far elaborare il gruppo finale.

Per quanto ne so il worktable è un heap (o sarebbe indicato nel piano come uno spool dell'indice). Tuttavia, quando provo a replicare lo stesso processo, sono necessarie solo 11 letture logiche.

CREATE TABLE #WorkTable 
    (
    OrderID INT, 
    CustomerID NCHAR(5) NULL, 
    Freight MONEY NULL, 
) 

DECLARE @Average MONEY 

PRINT 'Insert 3 Rows' 

INSERT INTO #WorkTable 
VALUES  (1, N'ALFKI', 29.46) /*Scan count 0, logical reads 1*/ 

INSERT INTO #WorkTable 
VALUES  (2, N'ALFKI', 61.02) /*Scan count 0, logical reads 1*/ 

INSERT INTO #WorkTable 
VALUES  (3, N'ALFKI', 23.94) /*Scan count 0, logical reads 1*/ 
PRINT 'Calculate AVG' 

SELECT @Average = Avg(Freight) 
FROM #WorkTable /*Scan count 1, logical reads 1*/ 
PRINT 'Return Rows - With the average column included' 

/*This convoluted query is just to force a nested loops plan*/ 
SELECT * 
FROM (SELECT @Average AS Avg_Freight) T /*Scan count 1, logical reads 1*/ 
     OUTER APPLY #WorkTable 
WHERE COALESCE(Freight, OrderID) IS NOT NULL 
     AND @Average IS NOT NULL 

PRINT 'Clear out work table' 

TRUNCATE TABLE #WorkTable 

PRINT 'Insert 1 Row' 

INSERT INTO #WorkTable 
VALUES  (4, N'ANATR', 39.92) /*Scan count 0, logical reads 1*/ 
PRINT 'Calculate AVG' 

SELECT @Average = Avg(Freight) 
FROM #WorkTable /*Scan count 1, logical reads 1*/ 
PRINT 'Return Rows - With the average column included' 

SELECT * 
FROM (SELECT @Average AS Avg_Freight) T /*Scan count 1, logical reads 1*/ 
     OUTER APPLY #WorkTable 
WHERE COALESCE(Freight, OrderID) IS NOT NULL 
     AND @Average IS NOT NULL 

PRINT 'Clear out work table' 

TRUNCATE TABLE #WorkTable 

PRINT 'Insert 1 Row' 

INSERT INTO #WorkTable 
VALUES  (5, N'ANTON', 22.00) /*Scan count 0, logical reads 1*/ 
PRINT 'Calculate AVG' 

SELECT @Average = Avg(Freight) 
FROM #WorkTable /*Scan count 1, logical reads 1*/ 
PRINT 'Return Rows - With the average column included' 

SELECT * 
FROM (SELECT @Average AS Avg_Freight) T /*Scan count 1, logical reads 1*/ 
     OUTER APPLY #WorkTable 
WHERE COALESCE(Freight, OrderID) IS NOT NULL 
     AND @Average IS NOT NULL 

PRINT 'Clear out work table' 

TRUNCATE TABLE #WorkTable 

PRINT 'Calculate AVG' 

SELECT @Average = Avg(Freight) 
FROM #WorkTable /*Scan count 1, logical reads 0*/ 
PRINT 'Return Rows - With the average column included' 

SELECT * 
FROM (SELECT @Average AS Avg_Freight) T 
     OUTER APPLY #WorkTable 
WHERE COALESCE(Freight, OrderID) IS NOT NULL 
     AND @Average IS NOT NULL 

DROP TABLE #WorkTable 
+0

C'è qualche differenza di prestazioni quando creiamo indici per tabelle Temp? – RGS

risposta

21

letture logiche sono conteggiati in modo diverso per piani di lavoro: c'è una 'logica leggere 'per riga leggere. Ciò non significa che i tavoli da lavoro siano in qualche modo meno efficienti di un tavolo di spool "reale" (piuttosto il contrario); le letture logiche sono solo in unità diverse.

Credo che il pensiero fosse che il conteggio delle pagine con hashing per le letture logiche del worktable non sarebbe molto utile perché queste strutture sono interne al server.La segnalazione delle righe inserite nello spooler delle letture logiche rende il numero più significativo ai fini dell'analisi.

Questa intuizione dovrebbe rendere chiaro il motivo della tua formula. I due spool secondari vengono letti completamente due volte (2 * COUNT (*)) e lo spool primario emette (numero di valori di gruppo + 1) righe come spiegato nel mio post di blog, fornendo il componente (COUNT (DISTINCT CustomerID) + 1) . Il più positivo è per la riga in più emessa dallo spool primario per indicare che il gruppo finale è terminato.

Paul

+1

Ah, questo spiega davvero le cose. Grazie mille per aver trovato il tempo di rispondere a questa domanda, che mi ha lasciato perplesso per alcuni mesi! –

+0

@MartinSmith & SQLkiwi - qui c'è abbastanza per la domanda derivata (ne posterò una se pensate che ci sia abbastanza carne) - Come si confrontano due query equivalenti per l'efficienza in cui si utilizzano le funzioni con finestra? Letture/scritture/cpu in profiler e piano di esecuzione sono i miei passi, ma questo complica la questione. – EBarr

+1

@EBarr Penso che la domanda abbia un merito, anche se avrebbe bisogno di una formulazione attenta per tenerlo focalizzato. Potrebbe essere meglio posato su http://dba.stackexchange.com/ La mia opinione è che la misura delle letture logiche è generalmente data * lontano * troppo peso. –

0

Nella formula vi darà le numberOfRows * 2 terrebbe vero a causa della funzione di ordinamento e Stream Mostra Aggregate nel diagramma di esecuzione sia bisogno di tutte le file per completare l'elaborazione. Si può comfirm una diminuzione di letture logiche quando viene aggiunto un "dove" clausola di:

  1. valore delle merci
  2. CustomerID
+0

Hai visto letture fisiche per questa query o durante lo sviluppo della formula? Sei in grado di utilizzare DBCC DROPCLEANBUFFERS e acquisire di nuovo le letture fisiche e logiche? Potresti aggiungere una clausola WHERE alla domanda e rieseguirla? Grazie –

Problemi correlati