索引不直接加速group by,而是通过提供有序数据避免排序和临时表;mysql默认先排序再分组,b+树索引匹配group by列顺序时可流式读取,消除using filesort和using temporary。

索引不直接“加速GROUP BY”,而是让数据库跳过排序和临时表——这才是性能瓶颈所在。
GROUP BY 实际上依赖排序,不是分组本身
MySQL 和 PostgreSQL 的 GROUP BY 在绝大多数情况下会先对数据按分组列排序,再逐组聚合。如果没有索引,数据库必须:全表扫描 → 写入磁盘临时表 → 外部排序(Using filesort)→ 分组。这个过程 I/O 开销极大。
而 B+ 树索引天然有序,只要索引顺序匹配 GROUP BY 列顺序,就能直接流式读取已排序的数据,省掉排序和临时表两步。
- ✅ 正确场景:
SELECT dept, COUNT(*) FROM emp WHERE status = 'active' GROUP BY dept+ 索引INDEX(status, dept)→ 过滤后数据天然按dept有序,Extra中不会出现Using temporary; Using filesort - ❌ 错误写法:
GROUP BY UPPER(name)或WHERE user_id = '123'(user_id是INT)→ 触发隐式转换,索引失效 - ⚠️ 注意:Hash 索引(如 Memory 引擎)对
GROUP BY完全无效,因为不提供顺序性
索引列顺序必须严格匹配 GROUP BY + WHERE 组合
最左前缀原则在这里是硬约束。索引 INDEX(a, b, c) 可用于:WHERE a = ? GROUP BY b(不行),但可用于:WHERE a = ? GROUP BY b, c 或 WHERE a > ? AND b = ? GROUP BY c。
- WHERE 条件列必须在最左,保证快速定位数据范围
- GROUP BY 列必须紧随其后且顺序一致,才能复用索引的有序性
- 如果还有
ORDER BY,也应接在后面(如INDEX(status, dept, create_time DESC)支持GROUP BY dept ORDER BY create_time DESC) - MySQL 8.0+ 支持降序索引;PostgreSQL 17 默认开启
enable_group_by_reordering,但依然依赖索引存在
松散索引扫描(Loose Index Scan)能进一步跳过冗余行
这是 MySQL 特有的一种高效模式,只在满足严苛条件时触发(EXPLAIN 显示 Using index for group-by):
-
GROUP BY列必须是索引最左连续前缀 - 只能使用
MIN()、MAX()聚合函数 - 不能有
HAVING过滤非索引字段,也不能引用索引中非分组列作非常量条件 - 例如:
SELECT group_id, MAX(gmt_create) FROM t WHERE group_id + 索引 <code>INDEX(group_id, gmt_create)可能触发该优化
它不扫完整索引,而是每组只取第一个/最后一个键值,对千万级表效果显著——但条件太苛刻,日常更应优先保障基础索引覆盖。
执行计划里藏着真实瓶颈,别猜,要看
所有优化建议都必须经 EXPLAIN FORMAT=TREE(MySQL 8.0+)或 EXPLAIN ANALYZE(PostgreSQL)验证。关键看三处:
-
type字段是否为ref/range(而非ALL或index) -
Extra是否出现Using temporary; Using filesort—— 出现即说明索引没生效或设计错位 -
rows预估扫描行数是否远大于最终分组数(比如扫描 500 万行,只返回 12 个分组,大概率可优化) - 若发现未走索引,优先检查字段类型一致性(如
WHERE col = 123vscol = '123')、NULL处理(WHERE col IS NOT NULL有时能激活索引)、统计信息是否过期(ANALYZE TABLE)
真正卡住 GROUP BY 的,从来不是聚合计算本身,而是排序与临时表。索引的作用,就是把“不得不做”的事,变成“根本不用做”。











