分组是怎么执行的
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 可建表达式索引,索引形式要和查询里写的一模一样才可能命中。
分组基数越大,哈希表或临时表就越吃内存。接近「每行一组」时,任何分组方式都要付出排序或建表成本,此时更该考虑先缩小输入行数。