Разница в первых двух подходах
Первый план тратит около 7 из 10 секунд в операторе окна золотника, так что это главная причина , это очень медленно. Он выполняет много операций ввода-вывода в базе данных tempdb. Моя статистика ввода / вывода и время выглядят так:
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.
Второй план способен избежать катушку, и , следовательно, целиком рабочий стол. Он просто захватывает первые 10 строк из кластеризованного индекса, а затем объединяет вложенные циклы с агрегацией (суммой), получаемой в результате отдельного сканирования кластерного индекса. Внутренняя сторона все еще заканчивается чтением всей таблицы, но таблица очень плотная, поэтому это достаточно эффективно с миллионами строк.
Table 'Table_1'. Scan count 11, logical reads 26093
SQL Server Execution Times:
CPU time = 1563 ms, elapsed time = 1671 ms.
Улучшение производительности
Columnstore
Если вам действительно нужен подход «онлайн-отчетности», вероятно, вам лучше подходит columnstore.
ALTER TABLE [dbo].[Table_1] DROP CONSTRAINT [PK_Table_1];
CREATE CLUSTERED COLUMNSTORE INDEX [PK_Table_1] ON dbo.Table_1;
Тогда этот запрос смехотворно быстр:
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Вот статистика с моей машины:
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.
Вы, вероятно, не собираетесь победить это (если вы не очень умный - хороший, Джо). Columnstore отлично умеет сканировать и собирать большие объемы данных.
Использование ROW
вместо RANGE
оконной функции
Вы можете получить очень похожую производительность со вторым запросом с помощью этого подхода, который был упомянут в другом ответе и который я использовал в приведенном выше примере columnstore ( план выполнения ):
SELECT TOP 10
seq,
value,
SUM(value) OVER (ORDER BY seq ROWS UNBOUNDED PRECEDING)
FROM dbo.Table_1
ORDER BY seq DESC;
Это приводит к меньшему количеству операций чтения, чем ваш второй подход, и никакая активность tempdb по сравнению с вашим первым подходом не происходит, потому что в памяти возникает спул окна :
... RANGE использует катушку на диске, в то время как ROWS использует катушку в памяти
К сожалению, время выполнения примерно такое же, как ваш второй подход.
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.
Основанное на схеме решение: асинхронные промежуточные итоги
Поскольку вы открыты для других идей, вы можете рассмотреть возможность асинхронного обновления «промежуточного итога». Вы можете периодически получать результаты одного из этих запросов и загружать их в таблицу «итогов». Итак, вы бы сделали что-то вроде этого:
CREATE TABLE [dbo].[Table_1_Totals]
(
[seq] [int] NOT NULL,
[running_total] [bigint] NOT NULL,
CONSTRAINT [PK_Table_1_Totals] PRIMARY KEY CLUSTERED ([seq])
);
Загружайте его каждый день / час / что угодно (это заняло около 2 секунд на моей машине с рядами 1 мм и могло быть оптимизировано):
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;
Тогда ваш отчетный запрос очень эффективен:
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;
Вот прочитанная статистика:
Table 'Table_1'. Scan count 0, logical reads 35
Table 'Table_1_Totals'. Scan count 1, logical reads 3
Основанное на схеме решение: итоговые суммы с ограничениями
Действительно интересное решение этой проблемы подробно рассмотрено в этом ответе на вопрос: написание простой банковской схемы: как я должен синхронизировать свои балансы с их историей транзакций?
Основной подход заключается в отслеживании текущего промежуточного итога в строке вместе с предыдущим промежуточным итогом и порядковым номером. Затем вы можете использовать ограничения для проверки правильности и актуальности текущих итогов.
Благодарим Пола Уайта за предоставление примера реализации схемы в этом разделе вопросов и ответов:
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)
)
);