优化器选hashagg而非stream aggregate,核心原因是输入数据未按group by列有序,导致流聚合不可用;隐式转换、函数操作或统计信息陈旧等均会破坏排序路径,迫使优化器退至哈希聚合。

优化器选 HashAgg 而不是 Stream Aggregate,核心就一条:它算出来走流聚合更贵——哪怕你建了索引,也可能被跳过。
输入数据没按 GROUP BY 列排序,流聚合直接不可用
流聚合要求输入行在 GROUP BY 列上严格有序,否则无法边读边分组。常见破坏排序的写法包括:
-
ORDER BY里混了非分组列(如GROUP BY user_id ORDER BY created_at) - 在
GROUP BY列上用了函数(GROUP BY UPPER(name)、DATE(created_at)) - 隐式类型转换(比如
user_id是varchar,但WHERE条件里写了user_id = 123,触发 int → varchar 转换,索引失效)
一旦排序路径断掉,优化器只能退到哈希聚合,因为它是唯一不依赖顺序的通用方案。
有索引但优化器主动放弃流聚合
MySQL 8.0 和 SQL Server 等现代引擎会做成本估算,即使 GROUP BY a, b 有复合索引,也可能不走流聚合:
- 估算分组数极高(比如
COUNT(DISTINCT order_id)配合大表),哈希表内存开销反而比排序+流式小 - 分组键太宽(如
GROUP BY long_url, user_agent),哈希桶内存占用爆炸,但排序代价可控时,可能仍选流聚合+临时文件 - 统计信息陈旧,优化器误判行数,低估排序开销,高估哈希溢出风险
这时看执行计划,会发现明明有索引扫描,却跟着一个 Sort 算子,再接 Stream Aggregate——说明优化器宁可多一次排序,也不信流聚合能稳住。
哈希聚合触发内存溢出(Spill)的典型信号
当看到这些现象,基本确认优化器“被迫”选了哈希聚合,且已撑不住:
- SQL Server 执行计划里出现
Warning: Hash warning: Hash bailout - PostgreSQL 的
EXPLAIN (ANALYZE)显示Spill Level > 0或disk: N kB - MySQL 8.0 的
EXPLAIN FORMAT=TREE中出现Using temporary; Using filesort并伴随高rows_examined
这不是配置问题,是数据特征和查询写法共同导致的——比如用完整 datetime 分组而不截断到日,分组基数从几千飙到百万级,哈希表必然爆。
真正难调的不是“怎么让优化器选哈希聚合”,而是“为什么它明明该选流聚合却没选”。这时候得盯死三件事:索引是否覆盖全部 GROUP BY 列且无计算、统计信息是否最新、执行计划里有没有被隐藏的隐式转换或函数封装。











