在SQL查询中执行COUNT、SUM、AVG等聚合函数并搭配GROUP BY子句时,数据库优化器会根据数据量、索引情况等因素选择不同的聚合执行方式,Hash Aggregate就是其中一种常见的执行算子。它基于哈希表实现分组和聚合计算,在处理中大规模数据集时往往比排序聚合效率更高。

Hash Aggregate出现的常见原因
数据库优化器选择Hash Aggregate作为聚合执行方式,通常和以下几个因素相关:
- 待聚合的数据量较大,排序聚合需要额外的排序开销,而哈希聚合不需要提前排序数据
- 分组列的基数适中,哈希表的冲突概率在可接受范围内,不会带来过高的额外开销
- 查询中没有可以利用的排序索引,无法直接使用排序聚合的优化路径
- 聚合操作不需要保持分组结果的顺序,哈希聚合输出的分组顺序是不确定的
哈希聚合的核心工作原理
哈希聚合的整体流程可以分为三个核心阶段,下面逐一拆解每个阶段的具体逻辑。
第一阶段:构建哈希表
数据库会先扫描参与聚合的数据行,对每一行的分组列计算哈希值,将哈希值作为键,聚合计算的初始状态作为值存入哈希表。如果同一分组的数据再次出现,就会更新哈希表中对应键的聚合状态。
比如执行以下查询时,数据库会先对dept_id列计算哈希值:
-- 统计每个部门的员工数量 SELECT dept_id, COUNT(*) AS emp_cnt FROM employee GROUP BY dept_id;
扫描第一条dept_id为10的数据时,计算10的哈希值,在哈希表中插入键为10哈希值、值为计数1的记录;扫描到下一条dept_id为10的数据时,找到对应哈希键的位置,将计数更新为2,以此类推。
第二阶段:处理哈希冲突
不同的分组列值可能计算出相同的哈希值,这时候就会产生哈希冲突。数据库通常会采用链地址法处理冲突,同一个哈希桶中存放所有哈希值相同的分组数据,再通过比较分组列的实际值确认是否为同一个分组,避免不同分组的数据被错误合并。
第三阶段:输出聚合结果
当所有数据行都扫描完成后,数据库会遍历哈希表中的所有有效分组,将每个分组的分组列值和对应的聚合计算结果输出,作为最终的查询结果。需要注意的是,哈希聚合输出的分组顺序和输入数据的顺序无关,也不保证任何固定的排序规则。
哈希聚合的适用场景和注意事项
哈希聚合适合以下场景:
- 分组聚合的数据量较大,排序带来的内存和CPU开销过高
- 分组列的重复值较多,哈希表的利用率较高
- 查询不需要分组结果保持有序,或者对结果顺序没有要求
如果分组列的基数极高,几乎每个分组都是唯一的,哈希表会占用大量内存,这时候优化器可能会选择其他聚合方式。另外如果内存不足以容纳完整的哈希表,数据库可能会将部分哈希表数据溢出到磁盘,这时候性能会有所下降。
简单示例演示哈希聚合过程
假设有以下employee表的部分数据:
| id | dept_id | salary |
|---|---|---|
| 1 | 10 | 8000 |
| 2 | 10 | 9000 |
| 3 | 20 | 7500 |
| 4 | 10 | 8500 |
| 5 | 20 | 8200 |
执行计算每个部门的平均工资的查询:
SELECT dept_id, AVG(salary) AS avg_salary FROM employee GROUP BY dept_id;
哈希聚合的处理过程如下:
- 扫描id=1的行,dept_id=10,计算哈希值h1,哈希表中无h1对应记录,插入键h1,值为{sum:8000, cnt:1}
- 扫描id=2的行,dept_id=10,哈希值h1已存在,更新值为{sum:17000, cnt:2}
- 扫描id=3的行,dept_id=20,计算哈希值h2,插入键h2,值为{sum:7500, cnt:1}
- 扫描id=4的行,dept_id=10,更新h1对应值为{sum:25500, cnt:3}
- 扫描id=5的行,dept_id=20,更新h2对应值为{sum:15700, cnt:2}
- 遍历哈希表,计算每个分组的平均值:dept_id=10的平均工资为25500/3=8500,dept_id=20的平均工资为15700/2=7850,输出最终结果
通过以上过程可以看出,哈希聚合不需要对全表数据进行排序,只需要一次扫描数据并维护哈希表,在中大规模数据场景下效率优势比较明显。理解它的工作原理,也有助于我们在编写聚合查询时更好地评估执行计划的合理性,针对性地做优化调整。
SQLHash_Aggregate聚合函数执行计划哈希聚合修改时间:2026-06-30 13:15:29