Diferença nas duas primeiras abordagens
O primeiro plano gasta cerca de 7 dos 10 segundos no operador de spool de janelas, portanto, esse é o principal motivo pelo qual é tão lento. Ele está executando muitas E / S no tempdb para criar isso. Minhas estatísticas de E / S e tempo são assim:
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.
O segundo plano é capaz de evitar o carretel e, portanto, a mesa de trabalho inteiramente. Ele simplesmente captura as 10 principais linhas do índice em cluster e, em seguida, um loop aninhado se junta à agregação (soma) resultante de uma verificação de índice em cluster separada. O lado interno ainda acaba lendo a tabela inteira, mas a tabela é muito densa, portanto é razoavelmente eficiente com um milhão de linhas.
Table 'Table_1'. Scan count 11, logical reads 26093
SQL Server Execution Times:
CPU time = 1563 ms, elapsed time = 1671 ms.
Melhorando a performance
Columnstore
Se você realmente deseja a abordagem "relatórios on-line", o columnstore provavelmente é sua melhor opção.
ALTER TABLE [dbo].[Table_1] DROP CONSTRAINT [PK_Table_1];
CREATE CLUSTERED COLUMNSTORE INDEX [PK_Table_1] ON dbo.Table_1;
Então, essa consulta é ridiculamente rápida:
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Aqui estão as estatísticas da minha 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.
Você provavelmente não vai superar isso (a menos que você seja realmente inteligente - legal, Joe). O columnstore é muito bom em digitalizar e agregar grandes quantidades de dados.
Usando ROW
a RANGE
opção de função de janela
Você pode obter um desempenho muito semelhante à sua segunda consulta com esta abordagem, mencionada em outra resposta e que usei no exemplo columnstore acima ( plano de execução ):
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Isso resulta em menos leituras que sua segunda abordagem e nenhuma atividade tempdb em relação à sua primeira abordagem porque o spool da janela ocorre na memória :
... O RANGE usa um carretel no disco, enquanto o ROWS usa um carretel na memória
Infelizmente, o tempo de execução é praticamente o mesmo que sua segunda abordagem.
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.
Solução baseada em esquema: totais assíncronos em execução
Como você está aberto a outras idéias, considere atualizar o "total total" de forma assíncrona. Periodicamente, você pode obter os resultados de uma dessas consultas e carregá-lo em uma tabela "totais". Então, você faria algo assim:
CREATE TABLE [dbo].[Table_1_Totals]
(
[seq] [int] NOT NULL,
[running_total] [bigint] NOT NULL,
CONSTRAINT [PK_Table_1_Totals] PRIMARY KEY CLUSTERED ([seq])
);
Carregue-o todos os dias / hora / o que for (isso levou cerca de 2 segundos na minha máquina com linhas de 1 mm e pode ser otimizado):
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;
Em seguida, sua consulta de relatórios é muito 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;
Aqui estão as estatísticas de leitura:
Table 'Table_1'. Scan count 0, logical reads 35
Table 'Table_1_Totals'. Scan count 1, logical reads 3
Solução baseada em esquema: totais em linha com restrições
Uma solução realmente interessante é abordada em detalhes nesta resposta à pergunta: Escrevendo um esquema bancário simples: Como devo manter meus saldos sincronizados com o histórico de transações?
A abordagem básica seria rastrear o total atual de corrida em linha, juntamente com o total anterior e o número de sequência. Em seguida, você pode usar restrições para validar que os totais em execução estejam sempre corretos e atualizados.
Agradecemos a Paul White por fornecer uma implementação de amostra para o esquema nestas Perguntas e Respostas:
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)
)
);