2010-11-20 20 views
21

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. 

Execution Plan

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 
+0

¿Hay alguna diferencia en el rendimiento cuando creamos índices para tablas Temp? – RGS

Respuesta

21

Lecturas lógicas se cuentan de manera diferente para mesas de trabajo: hay uno 'lógico leer 'por fila leer. Esto no significa que las tablas de trabajo sean de alguna manera menos eficientes que una tabla de carrete "real" (todo lo contrario); las lecturas lógicas son solo en unidades diferentes.

creo que la idea era que contar las de hash para la mesa de trabajo lecturas lógicas no sería muy útil, ya que estas estructuras son internas al servidor.Informes de filas en cola en el contador de lecturas lógicas hace que el número sea más significativo para fines de análisis.

Esta información debe explicar por qué su fórmula funciona. Los dos spools secundarios se leen completamente dos veces (2 * COUNT (*)), y el spool primario emite (número de valores de grupo + 1) filas como se explica en la entrada de mi blog, dando el componente (COUNT (DISTINCT CustomerID) + 1) . El más uno es para la fila extra emitida por el spool primario para indicar que el grupo final ha finalizado.

Paul

+1

Ah, eso explica las cosas. Muchas gracias por tomarse el tiempo para responder a esto, ya que me desconcertó durante unos meses. –

+0

@MartinSmith & SQLkiwi - ¿hay suficiente aquí para la pregunta derivada (publicaré una si crees que hay suficiente carne) - ¿Cómo se comparan dos consultas equivalentes para eficiencia en las que una usa funciones con ventana? Lee/escribe/cpu en el perfil y el plan de ejecución son mi ir, pero esto complica el asunto. – EBarr

+1

@EBarr Creo que la pregunta tiene sus méritos, aunque necesitaría una redacción cuidadosa para mantenerla enfocada. Podría ser mejor planteado en http://dba.stackexchange.com/ Mi opinión es que la medida de lecturas lógicas generalmente se da * muy * demasiado peso. –

0

En la fórmula le dará la numberOfRows * 2 serían aplicables debido a la función de clasificación y Stream Aggregate muestran en el diagrama de la ejecución de los dos necesitan todas las filas para completar el proceso. ¿Puede usted confirmar la categoría una disminución de lecturas lógicas cuando se añade una cláusula de "dónde" para:

  1. valor del flete
  2. CustomerID
+0

¿Has visto alguna lectura física para esta consulta o durante el desarrollo de la fórmula? ¿Puede usar DBCC DROPCLEANBUFFERS y capturar las lecturas físicas y lógicas nuevamente? ¿Podría agregar una cláusula WHERE al querey y volver a ejecutar? Gracias –

Cuestiones relacionadas