Unterschied in den ersten beiden Ansätzen
Der erste Plan verbringt ungefähr 7 der 10 Sekunden im Window Spool-Operator. Dies ist der Hauptgrund, warum er so langsam ist. Es wird eine Menge E / A in Tempdb ausgeführt, um dies zu erstellen. Meine Statistiken I / O und Zeit sehen folgendermaßen aus:
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.
Der zweite Plan kann die Spule und damit den Arbeitstisch vollständig umgehen. Es werden einfach die obersten 10 Zeilen aus dem Clustered-Index abgerufen und anschließend werden verschachtelte Schleifen mit der Aggregation (Summe) verknüpft, die aus einem separaten Clustered-Index-Scan hervorgeht. Die Innenseite liest immer noch die gesamte Tabelle, aber die Tabelle ist sehr dicht, so dass dies mit einer Million Zeilen einigermaßen effizient ist.
Table 'Table_1'. Scan count 11, logical reads 26093
SQL Server Execution Times:
CPU time = 1563 ms, elapsed time = 1671 ms.
Verbessernde Leistung
Columnstore
Wenn Sie wirklich den Ansatz der "Online-Berichterstellung" möchten, ist der Spaltenspeicher wahrscheinlich die beste Option.
ALTER TABLE [dbo].[Table_1] DROP CONSTRAINT [PK_Table_1];
CREATE CLUSTERED COLUMNSTORE INDEX [PK_Table_1] ON dbo.Table_1;
Dann ist diese Abfrage lächerlich schnell:
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Hier sind die Statistiken von meiner Maschine:
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.
Du wirst das wahrscheinlich nicht übertreffen (es sei denn, du bist wirklich schlau - nett, Joe). Columnstore ist verrückt gut darin, große Datenmengen zu scannen und zu aggregieren.
Verwenden Sie ROW
anstelle der RANGE
Fensterfunktionsoption
Mit diesem Ansatz, der in einer anderen Antwort erwähnt wurde und den ich im obigen Beispiel für den Spaltenspeicher verwendet habe ( Ausführungsplan ), können Sie eine sehr ähnliche Leistung wie bei Ihrer zweiten Abfrage erzielen :
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Dies führt zu weniger Lesevorgängen als bei Ihrem zweiten Ansatz und zu keiner Tempdb-Aktivität im Vergleich zu Ihrem ersten Ansatz, da die Fensterspool im Speicher auftritt :
... RANGE verwendet einen On-Disk-Spool, während ROWS einen In-Memory-Spool verwendet
Leider entspricht die Laufzeit in etwa Ihrer zweiten Vorgehensweise.
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.
Schemabasierte Lösung: Asynchrone laufende Summen
Da Sie offen für andere Ideen sind, können Sie die "laufende Summe" asynchron aktualisieren. Sie können die Ergebnisse einer dieser Abfragen regelmäßig in eine Tabelle "Summen" laden. Sie würden also so etwas tun:
CREATE TABLE [dbo].[Table_1_Totals]
(
[seq] [int] NOT NULL,
[running_total] [bigint] NOT NULL,
CONSTRAINT [PK_Table_1_Totals] PRIMARY KEY CLUSTERED ([seq])
);
Laden Sie es jeden Tag / jede Stunde / was auch immer (dies dauerte auf meiner Maschine mit 1-mm-Reihen ungefähr 2 Sekunden und konnte optimiert werden):
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;
Dann ist Ihre Berichtsabfrage sehr effizient:
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;
Hier sind die gelesenen Statistiken:
Table 'Table_1'. Scan count 0, logical reads 35
Table 'Table_1_Totals'. Scan count 1, logical reads 3
Schemabasierte Lösung: In-Row-Summen mit Einschränkungen
Eine wirklich interessante Lösung hierfür wird in dieser Antwort auf die Frage ausführlich beschrieben: Schreiben eines einfachen Bankschemas: Wie soll ich meine Salden mit dem Transaktionsverlauf synchronisieren?
Der grundlegende Ansatz wäre, die aktuelle laufende Summe in Reihe zusammen mit der vorherigen laufenden Summe und der Sequenznummer zu verfolgen. Anschließend können Sie mithilfe von Einschränkungen überprüfen, ob die laufenden Summen immer korrekt und aktuell sind.
Dank an Paul White für die Bereitstellung einer Beispielimplementierung für das Schema in dieser Frage und Antwort:
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)
)
);