Diferencia en los dos primeros enfoques
El primer plan pasa aproximadamente 7 de los 10 segundos en el operador Window Spool, por lo que esta es la razón principal por la que es tan lento. Realiza muchas E / S en tempdb para crear esto. Mis estadísticas de E / S y tiempo se ven así:
Table 'Worktable'. Scan count 1000001, logical reads 8461526
Table 'Table_1'. Scan count 1, logical reads 2609
Table 'Worktable'. Scan count 0, logical reads 0
SQL Server Execution Times:
CPU time = 8641 ms, elapsed time = 8537 ms.
El segundo plan es capaz de evitar el carrete y, por lo tanto, la mesa de trabajo por completo. Simplemente toma las 10 filas superiores del índice agrupado, y luego se unen los bucles anidados a la agregación (suma) que sale de un escaneo de índice agrupado separado. El lado interno todavía termina leyendo toda la tabla, pero la tabla es muy densa, por lo que esto es razonablemente eficiente con un millón de filas.
Table 'Table_1'. Scan count 11, logical reads 26093
SQL Server Execution Times:
CPU time = 1563 ms, elapsed time = 1671 ms.
Mejorando el desempeño
Almacén de columnas
Si realmente desea el enfoque de "informes en línea", el almacén de columnas es probablemente su mejor opción.
ALTER TABLE [dbo].[Table_1] DROP CONSTRAINT [PK_Table_1];
CREATE CLUSTERED COLUMNSTORE INDEX [PK_Table_1] ON dbo.Table_1;
Entonces esta consulta es ridículamente rápida:
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Aquí están las estadísticas de mi máquina:
Table 'Table_1'. Scan count 4, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 3319
Table 'Table_1'. Segment reads 1, segment skipped 0.
Table 'Worktable'. Scan count 0, logical reads 0
SQL Server Execution Times:
CPU time = 375 ms, elapsed time = 205 ms.
Es probable que no lo superes (a menos que seas realmente inteligente , agradable, Joe). Columnstore es muy bueno para escanear y agregar grandes cantidades de datos.
Usar en ROW
lugar de la RANGE
opción de función de ventana
Puede obtener un rendimiento muy similar a su segunda consulta con este enfoque, que se mencionó en otra respuesta y que utilicé en el ejemplo de almacén de columnas anterior ( plan de ejecución ):
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Resulta en menos lecturas que su segundo enfoque, y no hay actividad tempdb frente a su primer enfoque porque el spool de la ventana ocurre en la memoria :
... RANGE usa un carrete en disco, mientras que ROWS usa un carrete en memoria
Desafortunadamente, el tiempo de ejecución es casi lo mismo que su segundo enfoque.
Table 'Worktable'. Scan count 0, logical reads 0
Table 'Table_1'. Scan count 1, logical reads 2609
Table 'Worktable'. Scan count 0, logical reads 0
SQL Server Execution Times:
CPU time = 1984 ms, elapsed time = 1474 ms.
Solución basada en esquemas: totales acumulados asíncronos
Como está abierto a otras ideas, podría considerar actualizar el "total acumulado" de forma asincrónica. Puede tomar periódicamente los resultados de una de estas consultas y cargarla en una tabla de "totales". Entonces harías algo como esto:
CREATE TABLE [dbo].[Table_1_Totals]
(
[seq] [int] NOT NULL,
[running_total] [bigint] NOT NULL,
CONSTRAINT [PK_Table_1_Totals] PRIMARY KEY CLUSTERED ([seq])
);
Cárguelo todos los días / hora / lo que sea (esto tomó aproximadamente 2 segundos en mi máquina con filas de 1 mm, y podría optimizarse):
INSERT INTO dbo.Table_1_Totals
SELECT
seq,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING) as total
FROM dbo.Table_1 t
WHERE NOT EXISTS (
SELECT NULL
FROM dbo.Table_1_Totals t2
WHERE t.seq = t2.seq)
ORDER BY seq DESC;
Entonces su consulta de informes es muy eficiente:
SELECT TOP 10
t.seq,
t.value,
t2.running_total
FROM dbo.Table_1 t
INNER JOIN dbo.Table_1_Totals t2
ON t.seq = t2.seq
ORDER BY seq DESC;
Aquí están las estadísticas de lectura:
Table 'Table_1'. Scan count 0, logical reads 35
Table 'Table_1_Totals'. Scan count 1, logical reads 3
Solución basada en esquemas: totales en fila con restricciones
Una solución realmente interesante para esto se cubre en detalle en esta respuesta a la pregunta: Cómo escribir un esquema bancario simple: ¿Cómo debo mantener mis saldos sincronizados con su historial de transacciones?
El enfoque básico sería rastrear el total acumulado actual en fila junto con el total acumulado anterior y el número de secuencia. Luego puede usar restricciones para validar que los totales acumulados son siempre correctos y actualizados.
Gracias a Paul White por proporcionar una implementación de muestra para el esquema en estas preguntas y respuestas:
CREATE TABLE dbo.Table_1
(
seq integer IDENTITY(1,1) NOT NULL,
val bigint NOT NULL,
total bigint NOT NULL,
prev_seq integer NULL,
prev_total bigint NULL,
CONSTRAINT [PK_Table_1]
PRIMARY KEY CLUSTERED (seq ASC),
CONSTRAINT [UQ dbo.Table_1 seq, total]
UNIQUE (seq, total),
CONSTRAINT [UQ dbo.Table_1 prev_seq]
UNIQUE (prev_seq),
CONSTRAINT [FK dbo.Table_1 previous seq and total]
FOREIGN KEY (prev_seq, prev_total)
REFERENCES dbo.Table_1 (seq, total),
CONSTRAINT [CK dbo.Table_1 total = prev_total + val]
CHECK (total = ISNULL(prev_total, 0) + val),
CONSTRAINT [CK dbo.Table_1 denormalized columns all null or all not null]
CHECK
(
(prev_seq IS NOT NULL AND prev_total IS NOT NULL)
OR
(prev_seq IS NULL AND prev_total IS NULL)
)
);