GROUP BY字段顺序必须与复合索引最左前缀严格一致才能命中索引;否则触发全表扫描或filesort,如索引(region, status)无法支持GROUP BY status, region。

GROUP BY字段顺序不匹配索引最左前缀
MySQL、PostgreSQL 都要求 GROUP BY 字段顺序与复合索引的最左前缀严格一致,才能跳过排序直接分组。比如建了索引 (region, status),但写 GROUP BY status, region,优化器就无法利用该索引——它得先把数据全捞出来,再内存排序分组,触发 Using temporary; Using filesort。
常见陷阱:
- 误以为“索引包含所有分组字段”就够了,忽略顺序
- WHERE 条件用了范围查询(如
created_at > '2024-01-01'),而索引是(status, created_at),此时created_at后面的字段(如region)无法用于GROUP BY覆盖 - SQL Server 对顺序稍宽松,但乱序仍大概率退化为哈希聚合,不走索引查找
WHERE条件破坏索引覆盖能力
即使有索引,只要 WHERE 子句含范围条件,且该字段不在索引最左位置,后续字段就失效。例如表有索引 (item_code, avg_val, time),但查询是:
SELECT item_code, MAX(avg_val), MAX(time) FROM t WHERE time BETWEEN '2022-01-05' AND '2022-01-12' GROUP BY item_code;
这里 time 是范围条件,又在索引第三位,优化器只能扫描整个索引或回表,无法用 item_code 直接定位分组边界。
正确索引应把过滤字段前置:(time, item_code, avg_val) 或更优的 (item_code, time, avg_val)(如果 item_code 过滤性高)。
非BTREE索引或缺失索引
GROUP BY 依赖有序遍历,只有 BTREE 类型索引能提供物理/逻辑顺序;HASH、FULLTEXT、SPATIAL 索引都不行。另外,如果分组字段压根没索引,或者只有单列索引但实际按多列分组(如 GROUP BY a, b 却只对 a 建了索引),MySQL 就必须建临时表。
典型现象:
- 执行计划中出现
Using temporary,且没有Using index for group-by - 慢查询日志里
Rows_examined远大于结果行数 - 云数据库(如阿里云 RDS)上无法手动加
FORCE INDEX,只能靠索引设计修复
SELECT列表引入非分组、非聚合字段
哪怕索引完美匹配,只要 SELECT 里混入未聚合也未出现在 GROUP BY 的字段(如 SELECT user_id, name, COUNT(*) FROM t GROUP BY user_id),MySQL 5.7+ 会直接报错或强制走临时表——因为语义不明确,它不敢假设 name 和 user_id 是函数依赖关系。
这不是性能问题,是语义拦截。绕过方式(如 ANY_VALUE(name))不改变索引使用逻辑,但掩盖了本可通过窗口函数或主键关联规避的深层设计缺陷。
真正影响索引命中的,从来不是 GROUP BY 本身,而是你有没有让数据“自然有序”地落在索引结构里——顺序、覆盖、类型,三者缺一不可。

















