Differenza nei primi due approcci
Il primo piano impiega circa 7 dei 10 secondi nell'operatore Window Spool, quindi questo è il motivo principale per cui è così lento. Sta eseguendo molti I / O in tempdb per creare questo. Le mie statistiche I / O e il tempo si presentano così:
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.
Il secondo piano è in grado di evitare la bobina e quindi il piano di lavoro interamente. Prende semplicemente le prime 10 righe dall'indice cluster e quindi i loop nidificati si uniscono all'aggregazione (somma) che esce da una scansione dell'indice cluster separata. Il lato interno finisce ancora per leggere l'intero tavolo, ma il tavolo è molto denso, quindi questo è ragionevolmente efficiente con un milione di righe.
Table 'Table_1'. Scan count 11, logical reads 26093
SQL Server Execution Times:
CPU time = 1563 ms, elapsed time = 1671 ms.
Miglioramento delle prestazioni
columnstore
Se vuoi davvero l'approccio del "reporting online", il columnstore è probabilmente l'opzione migliore.
ALTER TABLE [dbo].[Table_1] DROP CONSTRAINT [PK_Table_1];
CREATE CLUSTERED COLUMNSTORE INDEX [PK_Table_1] ON dbo.Table_1;
Quindi questa query è incredibilmente veloce:
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Ecco le statistiche dalla mia macchina:
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.
Probabilmente non lo batterai (a meno che tu non sia davvero intelligente - bello, Joe). Columnstore è incredibilmente bravo a scansionare e aggregare grandi quantità di dati.
Utilizzo dell'opzione funzione ROW
anziché RANGE
finestra
Puoi ottenere prestazioni molto simili alla tua seconda query con questo approccio, che è stato menzionato in un'altra risposta e che ho usato nell'esempio columnstore sopra ( piano di esecuzione ):
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Risulta in meno letture rispetto al secondo approccio e nessuna attività tempdb rispetto al primo approccio perché lo spool della finestra si verifica in memoria :
... RANGE utilizza uno spool su disco, mentre ROWS utilizza uno spool in memoria
Sfortunatamente, il runtime è quasi lo stesso del tuo secondo approccio.
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.
Soluzione basata su schema: totali in esecuzione asincroni
Dato che sei aperto ad altre idee, potresti considerare di aggiornare il "totale parziale" in modo asincrono. È possibile prendere periodicamente i risultati di una di queste query e caricarli in una tabella "totali". Quindi faresti qualcosa del genere:
CREATE TABLE [dbo].[Table_1_Totals]
(
[seq] [int] NOT NULL,
[running_total] [bigint] NOT NULL,
CONSTRAINT [PK_Table_1_Totals] PRIMARY KEY CLUSTERED ([seq])
);
Caricalo ogni giorno / ora / qualunque cosa (ci sono voluti circa 2 secondi sulla mia macchina con file di 1 mm e potrebbe essere ottimizzato):
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;
Quindi la tua query sui rapporti è molto efficiente:
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;
Ecco le statistiche di lettura:
Table 'Table_1'. Scan count 0, logical reads 35
Table 'Table_1_Totals'. Scan count 1, logical reads 3
Soluzione basata sullo schema: totali in riga con vincoli
Una soluzione davvero interessante a questo è trattata in dettaglio in questa risposta alla domanda: Scrivere un semplice schema bancario: come devo mantenere i miei saldi in sincronia con la loro cronologia delle transazioni?
L'approccio di base sarebbe quello di tenere traccia del totale corrente corrente in fila insieme al totale corrente corrente e al numero progressivo. Quindi è possibile utilizzare i vincoli per convalidare i totali in esecuzione sono sempre corretti e aggiornati.
Ringraziamo Paul White per aver fornito un'implementazione di esempio per lo schema in queste domande e risposte:
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)
)
);