GROUP BY性能差主因是执行计划出现Using temporary或Using filesort,表明MySQL被迫使用磁盘临时表或排序;应按WHERE→GROUP BY→SELECT顺序创建复合索引,并合理使用SQL_BIG_RESULT等优化提示,必要时物化中间结果。

GROUP BY慢,先看执行计划里有没有Using filesort或Using temporary
这两个提示是千万级表GROUP BY性能崩坏的典型信号——MySQL被迫把分组逻辑从内存搬进磁盘临时表,IO直接拉满。不是数据量大就一定慢,而是执行路径没走对。
实操建议:
- 用
EXPLAIN FORMAT=TRADITIONAL跑一遍你的GROUP BY语句,重点盯Extra列 - 如果出现
Using temporary,说明索引无法支撑分组顺序,MySQL必须建临时表;Using filesort则意味着排序也失控了 - 注意:即使
WHERE条件命中了索引,GROUP BY字段没被覆盖,照样会触发临时表
复合索引要按WHERE → GROUP BY → SELECT字段顺序建
索引不是随便把字段堆进去就行。MySQL的B+树索引只有在“最左前缀匹配”且能按顺序输出分组键时,才能避免临时表。
实操建议:
- 假设语句是
SELECT user_id, COUNT(*) FROM orders WHERE status = 1 GROUP BY created_date,理想索引是INDEX(status, created_date)(status在前,因为是过滤条件;created_date紧随其后,让分组能直接利用索引顺序) - 如果还要
SELECT非分组字段(比如MAX(amount)),且不想回表,可加到索引末尾:INDEX(status, created_date, amount) - 别把
GROUP BY字段放索引最前面——除非没有WHERE条件;否则status等过滤字段缺失,索引利用率暴跌
SQL_BIG_RESULT提示可能比你想象中更实用
当MySQL预估分组结果集很大(比如上百万行),它默认倾向用磁盘临时表;但有时内存够、只是预估不准,加个提示就能切回内存模式。
实操建议:
- 在
SELECT前加SQL_BIG_RESULT提示:SELECT SQL_BIG_RESULT user_id, COUNT(*) FROM orders GROUP BY user_id - 这个提示告诉优化器:“结果集大,但请优先用内存哈希表”,适合分组键基数高但内存充足的情况
- 反向提示
SQL_SMALL_RESULT适用于结果行数极少(如GROUP BY status只有3–5个值),强制走排序而非哈希 - 注意:提示不解决索引缺失问题,只调优执行策略;先确保索引合理,再考虑提示
千万级分组别硬刚,考虑物化中间结果
有些场景,比如实时报表要频繁查GROUP BY day, city,每次扫全表根本扛不住。这时候索引和提示都只是止痛药,得换思路。
实操建议:
- 用
CREATE TABLE ... SELECT把高频分组结果存成汇总表,每天/每小时用INSERT ... ON DUPLICATE KEY UPDATE增量更新 - 汇总表主键设为分组字段组合(如
PRIMARY KEY(day, city)),查询直接走主键,毫秒级 - 如果业务允许几秒延迟,也可以用
MATERIALIZED VIEW(PostgreSQL)或物化视图替代方案(MySQL需手动维护) - 警惕陷阱:不要用
VIEW包装GROUP BY——它只是语法糖,每次查仍全表扫描
真正卡住的往往不是语法怎么写,而是没想清楚:这个分组结果是不是真的需要每次都重算?索引能省掉IO,但绕不开计算量;而物化,是把计算成本平摊到写入端。











