首页/数据/SQL 速查/GROUP BY 的执行方式与索引
性能与执行计划72 条

GROUP BY 的执行方式与索引

分组可通过索引顺序、排序或哈希完成:EXPLAIN 显示临时表或排序时,优先考虑让分组借用索引。

SQL 速查数据更新于 2026-10-13

分组是怎么执行的

GROUP BY 主要有三种执行方式:借用索引顺序直接分组、先排序再分组、以及把分组键放进内存哈希表聚合。前两种依赖有序输入,哈希聚合则靠内存换时间。EXPLAIN 里出现临时表或排序节点时,通常可以考虑让分组借用索引,省掉这一步。

基本信息

MySQL 关键提示Using index for group-by 表示借索引分组;Using temporary 表示走了临时表
PostgreSQL 节点HashAggregate 走哈希表;GroupAggregate 依赖有序输入
哈希内存受 work_mem 限制,超限分批落盘(EXPLAIN 中 Batches 大于 1)
索引要求分组列要匹配某个索引的最左前缀才能借上
MySQL 松散扫描Loose Index Scan 只读每个组的代表行,代价更低

典型使用场景

  • 报表按维度分组统计,先看能否借索引避开临时表
  • 大表分组前对比加索引前后的执行计划
  • 分组键基数很大时评估哈希聚合的内存占用
  • 分组后再接 HAVING 或 ORDER BY,判断能否复用同一个索引
  • 排查 GroupAggregate 前面多出来的 Sort 是哪来的

两库观察方式

两库观察方式
-- (user_id, status) 复合索引:分组列与索引最左前缀一致
EXPLAIN SELECT user_id, status, COUNT(*)
FROM orders
GROUP BY user_id, status;
-- MySQL 出现 Using index for group-by 表示借索引完成分组
-- 出现 Using temporary; Using filesort 则是走了临时表加排序

-- PostgreSQL:看用了哪种聚合节点
EXPLAIN (ANALYZE, BUFFERS)
SELECT user_id, status, COUNT(*) FROM orders GROUP BY user_id, status;
-- HashAggregate 走哈希,Batches 大于 1 说明落盘
-- GroupAggregate 依赖有序输入,前面通常有 Sort 或索引扫描

执行方式对照

执行方式触发条件优化方向
借索引分组分组列匹配索引最左前缀,且无阻碍条件建匹配的复合索引
排序后分组输入有序但不是索引顺序减少排序数据量,或加合适索引
哈希聚合无序输入、分组数适中提高 work_mem,避免 Batches 大于 1
临时表分组分组数很多或分组列含函数降低分组基数,避免对列做运算
对分组列做函数运算(例如按天分组)通常用不上普通索引:MySQL 8.0 支持函数索引,PostgreSQL 可建表达式索引,索引形式要和查询里写的一模一样才可能命中。
分组基数越大,哈希表或临时表就越吃内存。接近「每行一组」时,任何分组方式都要付出排序或建表成本,此时更该考虑先缩小输入行数。

易错点

  • 以为分组列上有索引就一定免排序:必须匹配索引最左前缀,且查询里不能有阻止使用索引的条件
  • 只看 MySQL 的 Using temporary 就下结论:PG 侧要看 HashAggregate 与 GroupAggregate 的差别,以及 Batches 是否大于 1
  • 对分组列做函数运算还想借索引:普通索引用不上,需要函数索引或表达式索引
  • 忽略哈希聚合落盘:work_mem 不够时分批处理,性能大幅下降,通过 Batches 或临时文件判断
  • 分组与后续 ORDER BY 各建各的索引:尽量让同一个索引同时满足分组顺序与排序顺序,否则多一次排序

数据来源与参考

  • MySQL 8.0 Reference Manual · GROUP BY Optimization
  • PostgreSQL Documentation · Aggregate Functions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌