GROUP BY 默认隐式排序导致性能断崖下跌,需用 ORDER BY NULL 关闭;索引顺序必须严格匹配 GROUP BY 字段;避免函数、确保 SELECT 列合规;大结果集用 SQL_BIG_RESULT。

GROUP BY 默认会触发隐式排序,必须显式关掉
MySQL 对 GROUP BY 的默认行为是返回有序结果,哪怕你没写 ORDER BY —— 这会强制走 filesort,尤其在百万级以上数据时,性能断崖下跌。不是“可能慢”,而是“必然慢”。
实操建议:ORDER BY NULL 是最轻量、最直接的解法:
- 加在
GROUP BY后面即可,例如:SELECT user_id, COUNT(*) FROM orders GROUP BY user_id ORDER BY NULL - 它不改变语义,只跳过排序阶段;执行计划里
Extra列不再出现Using filesort - 如果业务真需要排序,再单独加
ORDER BY,但务必确保该字段也在索引中(见下一条)
联合索引顺序必须严格匹配 GROUP BY 字段顺序
单列索引对 GROUP BY a, b 几乎无效;MySQL 只认最左前缀匹配,且分组时要求索引字段顺序与 GROUP BY 子句完全一致。
常见错误现象:EXPLAIN 显示 type = ALL 或 key_len 异常小(比如字段是 VARCHAR(255) 却只用了 10 字节),说明索引没对上。
实操建议:
- 先看
GROUP BY字段顺序,再建索引:如GROUP BY status, user_id→ 建INDEX idx_group (status, user_id) - 如果有
WHERE条件,把过滤性强的字段放最左:如WHERE status = 'paid' GROUP BY user_id, created_date→ 索引应为(status, user_id, created_date) - 避免在
GROUP BY中用函数:GROUP BY DATE(created_at)会让整个索引失效;改用冗余日期字段 + 索引
SELECT 列超出 GROUP BY 范围时,别硬扛,用 ANY_VALUE 或覆盖索引
MySQL 5.7+ 严格模式下,SELECT name, COUNT(*) FROM t GROUP BY dept_id 会直接报错 ERROR 1055;即使关了严格模式,也会触发 Using temporary,因为引擎要保“语义正确”——它得为每个分组挑一个 name 值,又不能随便选。
实操建议:
- 若只需任意一行值,显式写
ANY_VALUE(name),并确保name字段有索引(方便后续 WHERE 过滤) - 若需确定性结果(如最新一条),别在
GROUP BY里硬搞,改用窗口函数或子查询预过滤 - 最优解是覆盖索引:索引包含所有
SELECT非聚合字段 +GROUP BY字段,避免回表,例如SELECT user_id, status, COUNT(*) FROM orders GROUP BY user_id, status→ 索引(user_id, status)就够
大结果集时,SQL_BIG_RESULT 不是妥协,而是更稳的选择
当 GROUP BY 后预计产生几十万甚至百万级分组行时,MySQL 默认先尝试内存临时表(tmp_table_size 限制),超限就落盘、重建、归并——这个过程反复抖动,比直接走磁盘还慢。
实操建议:
- 加
SQL_BIG_RESULT提示:如SELECT SQL_BIG_RESULT COUNT(*), category FROM big_table GROUP BY category - 它跳过内存试探,直接用磁盘临时表 + 外部排序算法,I/O 更可预期
- 副作用明显:小结果集反而变慢;必须同步调大
sort_buffer_size,否则磁盘排序本身也卡
真正容易被忽略的点是:GROUP BY 性能从来不是单点问题。它卡住,往往是因为前面的 WHERE 没过滤干净、索引顺序没对齐、或者 SELECT 列设计本身就在制造歧义。优化时得从执行计划里的 Using temporary 和 Using filesort 这两个信号灯出发,一个一个切口往下挖。











