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.
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
C'è qualche differenza di prestazioni quando creiamo indici per tabelle Temp? – RGS