SQL Server选择Stream Aggregate需输入按GROUP BY列严格排序,否则插入Sort;Hash Aggregate适用于分组基数低且内存充足场景,但不可SARG表达式、统计信息不准或高基数易致spill或延迟输出。

输入数据未按 GROUP BY 列排序,强制触发 Hash Match(Aggregate)
Stream Aggregate 要求输入行严格按 GROUP BY 列升序或降序排列,否则优化器根本不会考虑它。常见破坏排序的场景包括:
- 没在分组列上建索引,或索引不覆盖查询(比如只建了
INDEX ON orders(custid),但查询是GROUP BY UPPER(custid)) -
JOIN后直接GROUP BY,而连接结果天然无序(即使两边都有索引,JOIN过程也会打乱顺序) - 子查询或 CTE 输出未显式
ORDER BY,且优化器无法复用上游排序(例如加了WHERE过滤后统计信息不准,误判顺序可保留)
一旦检测到无序,SQL Server 就会在执行计划里插入一个 Sort 运算符——此时你看到的是 Sort + Stream Aggregate 组合,但排序本身已成性能拐点。
分组基数低或中等,哈希表能塞进内存
当优化器估算分组键的唯一值数量(distinct count)不高时,会倾向选 Hash Aggregate:它单趟扫描即可完成聚合,理论复杂度 O(n),比先排序再流式处理更省 CPU 和 I/O。
- 典型场景:按
status(5 个枚举值)、region(几十个)、DATEADD(day, DATEDIFF(day, 0, orderdate), 0)(日粒度)分组 - 若实际分组数远超预估(比如
region列统计信息过期,显示只有 10 个值,实际有 500 个),哈希桶膨胀 → 内存不足 →Hash warning: Hash bailout→ spill 到tempdb,性能断崖下跌 - 检查方法:
DBCC SHOW_STATISTICS('orders', 'ix_orders_region')看distinct_range_rows是否接近真实分组数
GROUP BY 中用了不可 SARG 的表达式
任何让分组键无法直接匹配索引前导列的操作,都会关闭 Stream Aggregate 的门。
-
GROUP BY YEAR(orderdate)→ 索引orderdate无法跳过函数计算,必须全扫+哈希 -
GROUP BY UPPER(name)→ 即使name有索引,大小写转换也导致隐式转换或计算列失效 -
GROUP BY CASE WHEN ...或ISNULL(col, 'N/A')→ 同样绕过索引,强制哈希 - 修复方向:把计算提前物化,比如建计算列
order_date_only AS DATEADD(day, DATEDIFF(day, 0, orderdate), 0)并为其建索引
执行计划里出现 Hash Match(Aggregate) 但没警告,不代表没问题
没报 Hash warning 只说明这次没 spill,但 Hash Aggregate 仍需把全部输入行加载进内存哈希表才能输出第一行结果——这对带 LIMIT 或前端分页的查询极其不友好。
- Stream Aggregate 支持流式输出:遇到新分组键就立刻返回上一组结果,
TOP 10可能只扫前几千行 - Hash Aggregate 必须扫完全部输入,哪怕你只要前 10 组,它也得构建完整哈希表
- 真正要盯的不是“有没有哈希”,而是“有没有 spill”和“是否支持提前终止”——后者常被忽略,却直接影响用户体验

















