group by 是否走索引取决于是否满足“最左前缀+有序性”:字段须为索引最左连续前缀且未被函数、类型转换等破坏顺序;explain中key有值但extra含using temporary或filesort,说明索引未有效支撑分组。

GROUP BY 为什么有时不走索引?
MySQL 的 GROUP BY 是否走索引,取决于是否满足“最左前缀 + 有序性”两个硬条件。它不是独立优化项,而是依赖于执行计划中是否能复用已排序的索引数据——换句话说,GROUP BY 字段必须是某个可用索引的最左连续前缀,且查询没破坏该索引的自然顺序(比如没加 ORDER BY 非同一字段、没用函数包装、没类型隐式转换)。
常见不走索引现象:
- GROUP BY UPPER(name) → 函数导致索引失效
- GROUP BY user_id,但只有 (status, user_id) 联合索引 → 最左不是 user_id,无法跳过 status
- SELECT name, COUNT(*) FROM t GROUP BY name ORDER BY created_at → ORDER BY 字段不在 GROUP BY 索引中,触发 filesort + 临时表
如何让 GROUP BY 快速走索引?
核心动作是建对索引,而不是调优 SQL。优先考虑覆盖 + 顺序双重目标:
- 索引字段顺序必须严格匹配
GROUP BY列顺序(如GROUP BY a, b→ 建INDEX(a, b),不能是(b, a)) - 若还带
SELECT非分组字段(如SELECT a, b, MAX(c)),把c加到索引末尾形成覆盖:INDEX(a, b, c) - 避免在
GROUP BY列上做任何计算或类型转换,例如GROUP BY CAST(user_id AS CHAR)或GROUP BY CONCAT(id, '')会强制全表扫描 - 确认
sql_mode没开启ONLY_FULL_GROUP_BY外的宽松模式——虽然不影响索引选择,但可能掩盖语义错误,导致本该报错的查询“侥幸”跑起来却逻辑错乱
EXPLAIN 看什么才知 GROUP BY 走没走索引?
重点盯三个字段:type、key、Extra:
-
type是range、ref、index(注意:这里的index表示全索引扫描,比ALL好但未必高效)→ 说明用了索引 -
key显示实际生效的索引名 → 确认是不是你建的那个 -
Extra中没有Using temporary和Using filesort→ 这才是关键!出现任一者,说明 MySQL 不得不建临时表或额外排序,索引没被有效用于分组
示例:EXPLAIN SELECT dept, COUNT(*) FROM emp GROUP BY dept;
若返回 key: idx_dept 且 Extra: (空)→ 成功;若 Extra: Using temporary; Using filesort → 即使 key 有值,也说明索引未支撑完整分组流程。
GROUP BY 配合聚合函数时的索引陷阱
聚合本身不决定索引使用,但某些组合会让优化器放弃索引:
-
MIN()/MAX()在单列GROUP BY下可走索引(前提是该列为索引最左列),因为 MySQL 能利用 B+ 树有序性直接取极值 -
COUNT(*)和COUNT(1)在 InnoDB 中基本无差别,但COUNT(col)(非空判断)若col允许 NULL,且没被索引覆盖,则可能退化为行扫描 - 混合聚合如
SELECT a, MIN(b), AVG(c) FROM t GROUP BY a→ 只有当INDEX(a, b, c)存在时,MIN(b)才可能免排序;AVG(c)仍需遍历所有c值,无法跳过 - 如果
WHERE条件和GROUP BY字段不同源(比如WHERE status=1 GROUP BY user_id),即使有INDEX(status, user_id),也可能因范围查询后无法保证user_id有序而放弃索引分组
真正难的从来不是写对 GROUP BY,而是看懂执行计划里那句轻描淡写的 Using temporary —— 它背后是磁盘临时表、内存不足、甚至 OOM Kill。别只盯着 SQL 写法,先 EXPLAIN,再建索引,最后验证 Extra。










