mysql 8.0+ 中 group by 不再隐式排序,必须显式写 order by 才能保证顺序;若未加 order by null 且索引不匹配,即使建索引仍会触发 filesort 或 temporary。

GROUP BY排序没消失?先确认MySQL是否真用了索引
MySQL 8.0+ 默认对 GROUP BY 隐式加 ORDER BY,即使你没写。如果执行计划里 Extra 字段出现 Using filesort,说明排序没走索引——哪怕你建了索引,也可能因为顺序或覆盖不全被忽略。
关键判断依据是 EXPLAIN 输出中的 type(应为 range 或 index)、key(实际使用的索引名)、以及 Extra 是否含 Using temporary 或 Using filesort。
- 必须用
ORDER BY NULL显式关闭隐式排序,否则索引再好也白搭 - 复合索引字段顺序必须严格匹配
GROUP BY列的顺序(从左到右),不能跳过前导列 - 如果
SELECT中有非分组列(如MAX(price)),索引需覆盖这些列,否则仍会回表,但不影响排序消除
复合索引字段顺序怎么排?看GROUP BY子句原样抄
假设语句是 SELECT category, status, COUNT(*) FROM orders GROUP BY category, status,那索引必须是 (category, status),不能反过来,也不能中间插别的字段。
原因:B+树索引按字段顺序组织数据,只有前缀一致时才能保证后续字段天然有序。一旦顺序错乱(比如建了 (status, category)),MySQL无法利用该索引直接获得 category, status 的有序分组结果。
- 多列
GROUP BY必须 1:1 对应索引最左前缀,不可交换、不可省略中间列 - 如果还有
WHERE条件(如WHERE region = 'CN'),且想同时加速过滤和分组,索引应为(region, category, status)—— 把等值条件列放最左,再接GROUP BY列 - 范围查询(如
WHERE created_at > '2024-01-01')后不能接GROUP BY列,否则索引失效;此时应把GROUP BY列放在范围列之前,或接受排序无法避免
为什么加了索引还是出现Using temporary?
Using temporary 表示 MySQL 不得不用临时表存中间结果,通常是因为索引无法覆盖全部分组逻辑,比如 GROUP BY 涉及函数或表达式(GROUP BY YEAR(created_at))、或使用了不支持索引的字符集/排序规则(如 utf8mb4_0900_as_cs 下大小写敏感比较可能绕过索引)。
- 确保
GROUP BY列是原始列名,不是计算字段或别名 - 检查列字符集和排序规则是否一致;不同 collation 可能导致索引无法用于排序
- 若语句含
HAVING且条件无法下推(如HAVING COUNT(*) > 5),即使索引完美,MySQL 仍需临时表聚合后再过滤,Using temporary无法避免 - MySQL 5.7 及更早版本对
GROUP BY索引优化较弱,升级到 8.0+ 并开启optimizer_switch='group_by_optimization=on'(默认开启)
验证是否真正消除了排序:看执行计划+慢日志
光看 EXPLAIN 不够,要结合实际执行确认。在测试环境打开 slow_query_log 并设置 long_query_time = 0,跑一次查询,然后查 mysqld_slow.log 里的 Rows_examined 和 Sort_rows 字段。
-
Sort_rows: 0是排序彻底消失的铁证;只要大于 0,说明仍有内部排序发生 - 对比加索引前后
Rows_examined:如果从百万级降到几千,说明索引生效且大幅减少扫描量 - 注意
GROUP BY后跟ORDER BY的情况(如GROUP BY a ORDER BY b):这时需要额外索引覆盖ORDER BY字段,否则又会触发Using filesort
最易被忽略的是:开发常以为建了索引就万事大吉,但没加 ORDER BY NULL,或者 GROUP BY 列顺序和索引顺序差一位,结果性能毫无改善。










