You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多聚合查询时SQL Server Worktable逻辑读取量异常过高问题排查

SQL Server多聚合查询性能问题分析与优化方案

当从数百万行数据中查询多个聚合值时,SQL Server性能远低于PostgreSQL,无论是相同查询还是拆分单个聚合的查询,性能差距达数个数量级。通过STATISTICS IO分析发现,Worktable的逻辑读取量极高:处理5000万行数据时(源表仅读取92917页),Worktable逻辑读取量高达1.57亿次,该问题在SQL Server 2008R2至2019各版本中均存在。

复现问题的最简查询

select count(*), count(distinct m13), count(distinct m23) from foo

其中m13和m23为低基数外键,可通过以下脚本模拟数据(当前配置处理1000万行数据的50%):

数据模拟脚本

-- note: modify the cross join in the CTE for X in order to achieve the desired table size
if object_id('tempdb..#T') is null begin
    create table #T (n int primary key clustered, m13 tinyint not null, m23 tinyint not null);
    with -- ISNULL() removes the stain of perceived nullability
        D as (select isnull(value, 0) as value from (values (0), (1), (2),(3),(4),(5),(6),(7),(8),(9)) v (value)),
        H as (select isnull(d1.value * 10 + d0.value, 0) as value from D d1 cross join D d0),
        K as (select isnull(h.value * 10 + d.value, 0) as value from H h cross join D d),
        M as (select isnull(k1.value * 1000 + k0.value, 0) as value from K k1 cross join K k0),
        -- using ROW_NUMBER() here to allow easy composition via cross join, for freely choosing the target table size
        X as (select isnull(row_number() over (order by (select null)), 0) as value from D cross join M)
    insert into #T
        select value, value % 13, value % 23 from X;
end;
    
-- dial the desired percentage of rows to skip via the factor here
declare @threshold int = (select floor(0.50 * max(n) + 1) from #T);

select compatibility_level, @@version as "@@version" from sys.databases where name = db_name();
select '@threshold = ' + cast(@threshold as varchar);

set statistics time on;
set statistics io on;

select count(*)                                           from #T where n >= @threshold;
select count(*), count(distinct m13)                      from #T where n >= @threshold;
select count(*), count(distinct m13), count(distinct m23) from #T where n >= @threshold;

-- logical reads from 'Worktable' for @threshold at 50%:
--     10094 for 1e4 (   12 pages read from #T)
--    100942 for 1e5 (   96 pages read from #T)
--   1306626 for 1e6 (  933 pages read from #T)
--  14891285 for 1e7 ( 9295 pages read from #T)
-- 157450233 for 1e8 (92917 pages read from #T)

print char(13) + '### now the same in piece-meal fashion ... ###';

declare @cnt int, @m13 int, @m23 int = (select count(distinct m23) from #T where n >= @threshold);

select @cnt = count(*), @m13 = count(distinct m13) from #T where n >= @threshold;

select @cnt, @m13, @m23;

set statistics io off;
set statistics time off;

需求

寻求无需拆分单个聚合查询,仅通过改写查询促使SQL Server生成合理执行计划的方法,同时希望了解问题根源以优化后续SQL编写。

问题根源

SQL Server在处理多个COUNT(DISTINCT)聚合时,默认执行计划会采用嵌套流聚合逻辑:先为第一个COUNT(DISTINCT)构建哈希表去重,再基于该中间结果处理第二个COUNT(DISTINCT),导致Worktable需要反复读写中间数据。即使是低基数列场景,这种嵌套处理也会产生大量不必要的逻辑读取。而PostgreSQL会采用更高效的单次扫描或并行聚合处理多个去重统计,避免了额外的Worktable开销。

查询改写方案

以下几种改写方式可促使SQL Server生成更优执行计划,降低Worktable读取量:

方案1:窗口函数结合聚合

SELECT
    MAX(row_count) AS total_count,
    COUNT(DISTINCT m13) AS distinct_m13,
    COUNT(DISTINCT m23) AS distinct_m23
FROM (
    SELECT
        COUNT(*) OVER () AS row_count,
        m13,
        m23
    FROM #T
    WHERE n >= @threshold
) AS sub;

该改写让SQL Server先扫描一次源表获取所有必要数据,再在子查询结果上完成聚合,避免嵌套流聚合带来的Worktable开销。

方案2:CTE预扫描数据

WITH pre_scan AS (
    SELECT m13, m23 FROM #T WHERE n >= @threshold
)
SELECT
    (SELECT COUNT(*) FROM pre_scan) AS total_count,
    COUNT(DISTINCT m13) AS distinct_m13,
    COUNT(DISTINCT m23) AS distinct_m23
FROM pre_scan;

通过CTE统一源表扫描逻辑,查询优化器通常会选择单次扫描源表,再分别计算各个聚合值,减少Worktable的使用。

方案3:使用近似去重统计(SQL Server 2019+)

如果业务允许近似值,SQL Server 2019及以上版本支持APPROX_COUNT_DISTINCT函数,通过哈希算法高效计算去重计数,大幅减少Worktable读写:

select count(*), APPROX_COUNT_DISTINCT(m13), APPROX_COUNT_DISTINCT(m23) from #T where n >= @threshold;

方案4:强制哈希聚合

通过查询提示强制SQL Server使用哈希聚合而非流聚合,避免嵌套处理带来的额外开销:

select count(*), count(distinct m13), count(distinct m23) 
from #T 
where n >= @threshold
OPTION (HASH GROUP);

内容的提问来源于stack exchange,提问作者DarthGizka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 11:17:53