group by 慢主因是触发临时表和文件排序;需用explain确认,加索引要覆盖group by列及非聚合列,where先过滤减少数据量。

GROUP BY 为什么慢?先看执行计划
MySQL 的 GROUP BY 慢,往往不是语法问题,而是它默认触发了临时表(Using temporary)和文件排序(Using filesort)。用 EXPLAIN 查看执行计划,如果出现这两项,基本可以确定瓶颈在内存/磁盘排序阶段。
关键点:MySQL 5.7+ 默认使用 sql_mode=ONLY_FULL_GROUP_BY,这会让看似合法的查询被拒绝——不是性能问题,是语义校验失败,别误判成慢查询。
加索引不是随便加,要匹配 GROUP BY + 聚合字段
索引必须覆盖 GROUP BY 列 + 所有 SELECT 中的非聚合列(即“函数依赖列”),否则仍会回表或建临时表。例如:
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id;
理想索引是:ALTER TABLE orders ADD INDEX idx_user_id (user_id);
但如果写成:
SELECT user_id, status, COUNT(*) FROM orders GROUP BY user_id, status;
那就得建联合索引:ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
- 索引列顺序必须和
GROUP BY子句完全一致(最左前缀原则) - 如果
SELECT里有MAX(created_at),且created_at在索引中靠后,MySQL 可能仍无法避免排序 - 大字段(如
TEXT、长VARCHAR)不要放进索引,容易导致索引膨胀失效
用 WHERE 先过滤,别让 GROUP BY 处理全表
90% 的低效 GROUP BY 查询,是因为没加前置过滤条件。比如统计「昨天的订单数」,却写成:
SELECT DATE(created_at), COUNT(*) FROM orders GROUP BY DATE(created_at) ORDER BY 1 DESC LIMIT 1;
哪怕只想要 1 行结果,MySQL 仍要扫描全表分组再排序取 limit。正确做法是把时间范围压进 WHERE:
SELECT DATE(created_at), COUNT(*) FROM orders WHERE created_at >= '2024-06-09' AND created_at < '2024-06-10' GROUP BY DATE(created_at);
-
WHERE过滤越早越好,优先走索引范围扫描(range)而非全表(ALL) - 避免在
GROUP BY或SELECT中对字段做函数操作(如DATE(created_at)),会破坏索引使用;改用范围比较 - 如果业务允许,考虑冗余一个
order_date DATE字段并建索引,比每次计算快得多
临时表和排序参数调优不能只看 sort_buffer_size
当确实需要临时表时,光调大 sort_buffer_size 不够,还要关注三个关键参数:
-
tmp_table_size和max_heap_table_size必须同时调大,否则内存临时表会悄悄转成磁盘临时表(On disk) -
read_rnd_buffer_size影响排序后回表读取数据的速度,尤其在SELECT *+GROUP BY场景下明显 - 启用
optimizer_switch='greedy_search=off'有时能让优化器避开低效的嵌套循环计划(适用于多表 JOIN + GROUP BY)
但要注意:这些是服务器级调优,单条 SQL 改写(加索引、改写 WHERE)永远比调参更可控、更安全。
真正卡住的往往是那些隐式类型转换、JSON 字段参与分组、或者 LEFT JOIN 后对右表字段 GROUP BY 导致的笛卡尔积放大——这些细节比参数更值得花时间 inspect。











