He encontrado que en los planes de ejecución que usan spools de subexpresión comunes que las lecturas lógicas informadas son bastante altas para tablas grandes.¿Por qué son tan altas las lecturas lógicas para las funciones agregadas en ventanas?
Después de un poco de prueba y error, he encontrado una fórmula que parece ser válida para el script de prueba y el plan de ejecución a continuación. Worktable logical reads = 1 + NumberOfRows * 2 + NumberOfGroups * 4
No entiendo la razón por la cual esta fórmula es válida. Es más de lo que hubiera pensado que era necesario mirar el plan. ¿Alguien puede dar una ojeada a cuenta de lo que está pasando que explica esto?
o en su defecto ¿hay alguna manera de rastrear lo que la página se lee en cada lectura lógica para poder trabajar por mí mismo?
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;
salida
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.
Información adicional:
Hay una buena explicación de estos spools en el Capítulo 3 del Libro Query Tuning and Optimization y this blog post by Paul White.
En resumen, el iterador de segmento en la parte superior del plan añade una bandera para las filas que envía indicando cuando es el inicio de una nueva partición. El spool de segmento primario obtiene una fila a la vez del iterador de segmento y lo inserta en una tabla de trabajo en tempdb. Una vez que obtiene la bandera que indica que un nuevo grupo ha comenzado, devuelve una fila a la entrada superior del operador de bucles anidados. Esto provoca que el agregado de secuencia se invoque sobre las filas en la tabla de trabajo, el promedio se calcula y luego este valor se vuelve a unir con las filas en la tabla de trabajo antes de que la tabla de trabajo se trunque esté lista para el nuevo grupo. El spool de segmento emite una fila ficticia para procesar el grupo final.
Por lo que entiendo la mesa de trabajo es un montón (o sería denota en el plan como un carrete de índice). Sin embargo, cuando intento replicar el mismo proceso, solo necesito 11 lecturas lógicas.
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
¿Hay alguna diferencia en el rendimiento cuando creamos índices para tablas Temp? – RGS