stream aggregate 仅在输入数据按 group by 列严格有序时启用,内存恒定、支持提前终止;hash aggregate 用于无序数据且分组基数适中场景,但哈希溢出至 tempdb 会导致性能断崖下跌。

Stream Aggregate 和 Hash Aggregate 不是“随便选的”,而是优化器根据数据是否已排序和分组基数大小这两个硬性条件,权衡内存、CPU、I/O后做出的物理执行选择。
Stream Aggregate 什么时候出现?为什么快?
它只在输入数据按 GROUP BY 列严格有序时启用。这种有序可以来自:
- 聚集索引扫描(如按主键分组)
- 非聚集索引覆盖(如
CREATE INDEX idx ON Orders(custid)后对custid分组) - 显式
ORDER BY+ 优化器判断排序可复用 - 子查询或 CTE 提前排序且未被打乱
Stream Aggregate 是流式处理:逐行读入,遇到新分组值就立刻输出上一组结果。这意味着:
- 内存占用恒定(只存当前组的
SUM、COUNT等中间状态) - 支持提前终止(比如加了
LIMIT 10,算完 10 组就停) - 没有哈希冲突、不 spill 到磁盘
但一旦输入无序,SQL Server 就必须先加一个 Sort 运算符——这时执行计划里会同时出现 Sort + Stream Aggregate,性能拐点就在这儿。
Hash Aggregate 为什么被选中?它真慢吗?
当优化器判断:
- 输入数据无序,且建索引成本高(比如临时表、CTE、JOIN 后结果)
- 分组列基数低到中等(比如城市名、状态码),哈希表能塞进内存
- 表很大,排序开销 > 哈希构建开销
Hash Aggregate 就会上线。它的核心是内存哈希表:
- 键 =
GROUP BY列值(如UPPER(name)会导致无法复用索引,强制哈希) - 值 = 聚合中间状态(
SUM、COUNT、MIN等)
它不依赖顺序,单趟扫描即可完成分组更新。理论复杂度 O(n),但:
- 所有行处理完才能输出结果(不支持流式分页)
- 若哈希桶过多或单桶过大(如分组列含大量 NULL 或重复值),触发
Hash warning: Hash bailout - 出现
Spill Level > 0说明部分哈希表写到了tempdb,性能断崖下跌
怎么让优化器倾向 Stream Aggregate?
关键不是“命令它用哪个”,而是给它用 Stream Aggregate 的条件:
- 在常用分组列上建合适索引(注意隐式转换:如果
WHERE name = 'abc'但列是varchar而参数是nvarchar,索引失效 → 强制哈希) - 避免在
GROUP BY中用函数:GROUP BY YEAR(orderdate)比GROUP BY orderdate更难走索引,也更易触发哈希 - 对大表聚合,优先考虑截断高基数列:用
DATEADD(day, DATEDIFF(day, 0, orderdate), 0)替代完整datetime,减少分组数 - 检查执行计划里
Hash Match(Aggregate)是否伴随警告;有警告就说明内存真不够,加 RAM 或缩小输入集比调优 SQL 更直接
真正卡住性能的,往往不是“用了哈希”这个事实,而是哈希表溢出到磁盘那一刻——那之后的每一步都在等 I/O。盯住 Hash warning: Hash bailout 和 Spill Level,比纠结“该不该用哈希”有用得多。











