group by变慢主因是执行计划失控,explain出现using temporary或using filesort即表明mysql放弃索引直取而启用临时表与额外排序;根本解决需确保索引顺序严格匹配group by字段、覆盖非聚合列、避免函数和隐式转换。

GROUP BY 变慢不是语法问题,而是执行计划失控的明确信号:只要 EXPLAIN 的 Extra 列出现 Using temporary 或 Using filesort,说明 MySQL 已放弃索引直取,转而用内存/磁盘临时表 + 额外排序完成分组。
为什么加了索引还是触发 Using temporary
索引存在 ≠ 能用于 GROUP BY。常见硬伤包括:
-
GROUP BY a, b但索引是(b, a)或单列(a)—— 最左前缀不匹配,无法利用物理有序性 -
SELECT a, b, SUM(c),索引只建了(a, b),c得回表查,MySQL 认为不如全扫+临时表更省事 -
WHERE created_at > '2024-01-01'是范围条件,若放在复合索引中间(如(shop_id, created_at, user_id)),user_id部分索引直接失效 - 隐式类型转换:
WHERE user_id = '123'(user_id是BIGINT)→ 索引失效,GROUP BY 失去依托
如何让 Using filesort 消失
本质是让数据在磁盘/内存中天然有序,省去额外排序步骤:
- 确保
GROUP BY字段顺序与索引列顺序严格一致;例如GROUP BY status, category,索引必须是(status, category)或(status, category, note),不能是(category, status) - 避免在
GROUP BY中用函数:GROUP BY DATE(created_at)会强制逐行计算,改用WHERE created_at >= '2024-01-01' AND created_at + <code>GROUP BY DATE(created_at)等价写法 - 如果业务真不需要结果有序,显式加
ORDER BY NULL,可抑制优化器自动补排序逻辑 —— 但仅对Using filesort有效,若Using temporary还在,它不解决根本问题
覆盖索引到底要怎么建
覆盖索引不是字段堆得多就好,关键看是否真正“覆盖”整条访问链:
-
SELECT category, COUNT(*) FROM t WHERE status = 'paid' GROUP BY category→ 最优索引是INDEX idx_status_category (status, category),status放最左满足等值过滤,category紧随其后支撑分组有序 -
SELECT user_id, SUM(amount), MAX(note) FROM orders GROUP BY user_id→ 若想覆盖,索引至少得是(user_id, amount, note);只建(user_id)或(user_id, amount)都不行,因为MAX(note)仍需回表 - 别在索引里塞无关字段:比如
note是TEXT,放进索引会显著增大 B+ 树体积,拖慢所有使用该索引的查询
最容易被忽略的一点:GROUP BY 性能拐点往往不在数据量突破千万时,而在索引失效 + 并发叠加的那一刻——这时候盯着 EXPLAIN 看 Extra 比调 tmp_table_size 管用十倍。











