Using temporary表示MySQL正在真实创建内存或磁盘临时表处理中间结果,非提示而是实际执行开销;其根本原因是GROUP BY、ORDER BY、SELECT字段与索引配合断裂,根治方法是建立覆盖索引而非调大tmp_table_size。
Using temporary 真的在建表,不是“提示”而是执行动作
navicat 的执行计划里出现 using temporary,代表 mysql 正在内存或磁盘上真实创建临时表来存中间结果——不是警告,也不是预估,是正在发生的体力开销。一旦数据量超过 tmp_table_size 和 max_heap_table_size 中的较小值,就会立刻落盘,触发磁盘 i/o,查询延迟直接跳升 5–10 倍。
- 哪怕只返回 10 行,只要优化器判定无法流式分组/去重/排序,就必须攒够一批数据再处理
-
SHOW STATUS LIKE 'Created_tmp_disk_tables'上升,基本等于你正经历落盘临时表 - 并发稍高时,多个连接争抢内存临时表空间,会加剧
Copying to tmp table on disk阶段耗时
GROUP BY 和 ORDER BY 字段不一致是最常见诱因
MySQL 要想跳过临时表,必须能按 GROUP BY 字段顺序扫描、归并,同时满足 ORDER BY 的输出顺序。两者字段不一致(比如 GROUP BY user_id 却 ORDER BY created_at DESC),优化器就无法复用索引有序性,只能先分组、再排序,中间必然经过临时表。
- 正确顺序示例:查询
SELECT user_id, COUNT(*) FROM t GROUP BY user_id ORDER BY user_id DESC,配合索引INDEX(user_id)可跳过临时表 - 错误写法:
GROUP BY user_id ORDER BY COUNT(*) DESC—— 聚合值无法被索引预排序,必走临时表 - 复合场景下,
GROUP BY a, b ORDER BY a, b才可能跳过;若写成ORDER BY b, a,即使字段相同,顺序错一位也失效
索引没覆盖 SELECT 列也会强制建临时表
当 SELECT 列包含非分组字段且无聚合函数(如 SELECT name, COUNT(*) FROM t GROUP BY user_id),MySQL 无法确定每组该取哪条 name,就会用临时表兜底——这是 SQL 标准合规性检查,不是性能优化问题,改不了逻辑就绕不开。
- 解决办法只有两种:补上聚合函数(如
MAX(name)),或确保所有非分组字段都在索引中作为后缀(覆盖索引) - 例如查询
WHERE status = 'active' GROUP BY user_id,索引应建为INDEX(status, user_id, name),而非只建INDEX(user_id) - 索引末尾多一个无关字段(比如加了个
updated_at),可能让最左前缀匹配失败,整个索引对 GROUP BY 失效
别信“调大 tmp_table_size 就能快”
把 tmp_table_size 从默认 16MB 改到 256MB,看起来能撑住更多内存临时表,但实际常白忙:两个配置必须设为相同值,否则 MySQL 取小者;已建立的连接不会动态生效;更关键的是,它不减少扫描行数、不消除排序逻辑、不缓解锁竞争——只是把“落盘”延后,把问题藏得更深。
- 真正有效的解法永远是:让 GROUP BY 字段能被索引顺序扫描,且 SELECT 列被覆盖
- 每次改索引后,务必
ANALYZE TABLE table_name更新统计信息,否则优化器可能继续误判 -
Using temporary可能藏在子查询里——主查询EXPLAIN看不到,得单独对子查询执行EXPLAIN











