Différence dans les deux premières approches
Le premier plan passe environ 7 des 10 secondes dans l'opérateur de bobine de fenêtre, c'est donc la principale raison pour laquelle il est si lent. Cela crée beaucoup d'E / S dans tempdb pour créer cela. Mes statistiques d'E / S et de temps ressemblent à ceci:
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.
Le deuxième plan est capable d'éviter la bobine, et donc la table de travail entièrement. Il récupère simplement les 10 premières lignes de l'index clusterisé, puis une boucle imbriquée se joint à l'agrégation (somme) issue d'un balayage d'index cluster séparé. La face intérieure finit toujours par lire la table entière, mais la table est très dense, donc cela est raisonnablement efficace avec un million de lignes.
Table 'Table_1'. Scan count 11, logical reads 26093
SQL Server Execution Times:
CPU time = 1563 ms, elapsed time = 1671 ms.
Amélioration des performances
Columnstore
Si vous voulez vraiment l'approche du «reporting en ligne», columnstore est probablement votre meilleure option.
ALTER TABLE [dbo].[Table_1] DROP CONSTRAINT [PK_Table_1];
CREATE CLUSTERED COLUMNSTORE INDEX [PK_Table_1] ON dbo.Table_1;
Ensuite, cette requête est ridiculement rapide:
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Voici les statistiques de ma machine:
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.
Vous n'allez probablement pas battre cela (à moins que vous ne soyez vraiment intelligent - gentil, Joe). Columnstore est extrêmement bon pour analyser et agréger de grandes quantités de données.
Utiliser ROW
plutôt que l' RANGE
option de fonction de fenêtre
Vous pouvez obtenir des performances très similaires à votre deuxième requête avec cette approche, qui a été mentionnée dans une autre réponse, et que j'ai utilisée dans l'exemple columnstore ci-dessus ( plan d'exécution ):
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Il en résulte moins de lectures que votre deuxième approche et aucune activité tempdb par rapport à votre première approche car le spool de fenêtre se produit en mémoire :
... RANGE utilise un spool sur disque, tandis que ROWS utilise un spool en mémoire
Malheureusement, l'exécution est à peu près la même que votre deuxième approche.
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.
Solution basée sur un schéma: totaux cumulés asynchrones
Puisque vous êtes ouvert à d'autres idées, vous pouvez envisager de mettre à jour le "total cumulé" de manière asynchrone. Vous pouvez périodiquement prendre les résultats d'une de ces requêtes et les charger dans une table "totaux". Vous feriez donc quelque chose comme ceci:
CREATE TABLE [dbo].[Table_1_Totals]
(
[seq] [int] NOT NULL,
[running_total] [bigint] NOT NULL,
CONSTRAINT [PK_Table_1_Totals] PRIMARY KEY CLUSTERED ([seq])
);
Chargez-le tous les jours / heures / peu importe (cela a pris environ 2 secondes sur ma machine avec des rangées de 1 mm et pourrait être optimisé):
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;
Ensuite, votre requête de rapport est très efficace:
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;
Voici les statistiques de lecture:
Table 'Table_1'. Scan count 0, logical reads 35
Table 'Table_1_Totals'. Scan count 1, logical reads 3
Solution basée sur un schéma: totaux en ligne avec contraintes
Une solution vraiment intéressante à cela est traitée en détail dans cette réponse à la question: Écrire un schéma bancaire simple: Comment dois-je garder mes soldes en synchronisation avec l'historique de leurs transactions?
L'approche de base serait de suivre le total cumulé actuel en ligne avec le total cumulé précédent et le numéro de séquence. Ensuite, vous pouvez utiliser des contraintes pour valider que les totaux cumulés sont toujours corrects et à jour.
Nous remercions Paul White d' avoir fourni un exemple d'implémentation du schéma dans ce Q&R:
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)
)
);