using temporary 表示 group by 未利用索引分组,必须重建临时表,导致 i/o 与 cpu 双重开销;需按 where→group by→select 顺序创建联合索引,并避免在 group by 中使用函数。

看到 Using temporary 就说明 GROUP BY 正在硬扫全表建临时表,这不是慢的问题,是架构级失效——必须从索引结构和查询写法两头堵。
为什么EXPLAIN里出现Using temporary就等于性能已崩
MySQL 执行 GROUP BY 时,本质需要「相同分组键的行物理相邻」才能边扫边聚。没索引支撑,它只能把所有符合条件的行先塞进内存或磁盘临时表,再排序、再遍历分组。3000万行数据,这个过程就是 I/O + CPU 双烤机。
-
Using temporary不是警告,是确诊书:索引没被用于分组阶段,哪怕key列显示用了索引,只要Extra里有它,索引就白搭 - 区分度低的字段(比如
status只有 3 个值)放索引最左,可能让优化器直接放弃走索引——要优先把高区分度的WHERE字段放最左 -
key_len异常大(如 VARCHAR(255) 字段只查前 10 字符却显示 765),说明索引定义和实际查询不匹配,索引形同虚设
联合索引必须按 WHERE → GROUP BY → SELECT 顺序建
不是字段堆一起就行,MySQL 的松散索引扫描(Loose Index Scan)只认严格顺序。B+ 树必须能「顺着索引顺序读,相同分组值天然连续」,才跳过临时表。
- 语句:
SELECT user_id, COUNT(*) FROM orders WHERE status = 1 AND created_at > '2024-01-01' GROUP BY user_id, product_type - 正确索引:
ALTER TABLE orders ADD INDEX idx_status_created_uid_pt (status, created_at, user_id, product_type) - 错误索引:
INDEX(user_id, product_type, status)——WHERE条件无法用最左前缀,直接废掉 - 如果还要
SELECT MAX(amount),必须把amount加到索引末尾,否则回表会触发Using temporary
别在GROUP BY里用函数,DATE(created_at)等于自废武功
函数会让索引完全失效,MySQL 只能全表扫描再逐行计算。这不是“可能慢”,是“必然慢”,且无法通过调参绕过。
- 错误写法:
GROUP BY DATE(created_at)—— 即使你建了INDEX(created_at),也毫无作用 - 高频按天统计:加冗余字段
created_date DATE,建索引INDEX(created_date, user_id),查询改用WHERE created_date = '2024-06-01' GROUP BY created_date - 低频灵活查询:先用范围条件砍数据量,
WHERE created_at >= '2024-06-01' AND created_at
子查询里的GROUP BY比主查询更难优化
相关子查询中嵌套 GROUP BY,优化器基本放弃索引推导,Using temporary 几乎必现。重写为 JOIN 是首选,实在不行才动索引。
- 低效写法:
SELECT a.id, (SELECT COUNT(*) FROM orders o WHERE o.user_id = a.id GROUP BY o.user_id) cnt FROM users a - 高效改写:
SELECT a.id, COALESCE(o.cnt, 0) FROM users a LEFT JOIN (SELECT user_id, COUNT(*) cnt FROM orders GROUP BY user_id) o ON o.user_id = a.id - 子查询部分必须有索引:
INDEX(user_id),否则内层依然Using temporary - 若子查询带
WHERE status = 'completed',索引得是INDEX(status, user_id),顺序不能颠倒
真正卡住人的往往不是索引建不建,而是 WHERE 和 GROUP BY 字段不在同一联合索引里,或者 SELECT 里多了一个没覆盖的字段——这些细节一漏,前面所有优化都归零。











