Using temporary表示MySQL已放弃索引分组,正在写磁盘临时文件,是I/O瓶颈已成型;必须检查type是否为ALL/index、key_len是否匹配、Extra是否有Using where,否则WHERE未走索引导致过滤后移。

EXPLAIN里出现Using temporary就该停手检查
只要EXPLAIN的Extra列里有Using temporary,说明MySQL已放弃索引分组,正在往磁盘写#sql_*临时文件——这不是“可能慢”,是I/O瓶颈已成型。此时调参只是拖延时间,必须先确认三件事:type是不是ALL或index、key_len是否匹配字段实际长度、Extra里有没有Using where。没Using where往往意味着WHERE条件根本没进索引,过滤动作被挪到了分组之后。
GROUP BY字段顺序决定联合索引怎么建
单列索引对多字段分组基本无效。真正起效的是严格按GROUP BY子句顺序创建的联合索引,且要兼顾WHERE条件的最左前缀匹配。例如查询:SELECT region, user_tier, COUNT(*) FROM orders o JOIN users u ON o.user_id = u.id WHERE u.status = 'active' GROUP BY region, user_tier,索引应为INDEX(status, region, user_tier),而不是INDEX(region, user_tier)。如果还要AVG(order_amount),就把order_amount加到索引末尾形成覆盖,避免回表。
高基数字段直接进GROUP BY等于主动触发磁盘落表
user_id、ip_address、email这类高基数字段一旦出现在GROUP BY里,分组桶数量会呈乘积级膨胀,内存临时表瞬间超限。解决方式不是调大tmp_table_size,而是降维:
- 用
JOIN把user_id映射成tier(VIP/普通/试用)再分组 - 用IP库把
ip_address转成country或isp再聚合 - 邮箱提取
SUBSTRING_INDEX(email, '@', -1)得域名,再按域名分组
JOIN条件走索引,否则只是把性能问题从GROUP BY转移到JOIN。
别信函数能绕过索引失效
GROUP BY DATE(created_at)、GROUP BY YEAR(month)这类写法会让所有索引失效,MySQL只能全表扫描再逐行计算。MySQL 5.7不支持函数索引,INDEX(DATE(created_at))完全无效。可行解只有两个:
- 加冗余字段如
created_date DATE,建索引INDEX(created_date, user_id),查询改用WHERE created_date BETWEEN ... GROUP BY created_date - 用范围条件提前过滤,比如
WHERE created_at >= '2026-09-01' AND created_at ,先砍掉90%数据再分组
tmp_table_size和max_heap_table_size必须设为相同值,且不宜超过物理内存25%,否则并发一高就OOM。
真正卡住的点往往不在SQL本身,而在分组维度设计是否贴合业务真实需要——一个报表是否真要按百万级user_id展开,还是只需要它归属的3档tier?这种判断比写对索引更难,也更容易被忽略。

















