EXPLAIN未报临时表但SHOW PROCESSLIST卡在Copying to tmp table,是因为EXPLAIN仅预估执行计划,不模拟真实内存压力;而Copying to tmp table是运行时行为——当结果集超过tmp_table_size与max_heap_table_size的较小值时,MySQL才实际将内存临时表转为磁盘临时表。

为什么 EXPLAIN 里没报临时表,但 SHOW PROCESSLIST 却卡在 Copying to tmp table
因为 EXPLAIN 只预估执行计划,不模拟真实内存压力;而 Copying to tmp table 是运行时行为——当结果集超过 tmp_table_size 和 max_heap_table_size 中的较小值时,MySQL 才会真正创建临时表(先内存后落盘)。所以即使 EXPLAIN 显示“Using filesort”或“Using temporary”,也不等于一定会触发该状态;反过来,没看到这些提示,也可能因数据量突增而实际触发。
常见诱因包括:
-
ORDER BY或GROUP BY的列没索引,且返回行数远超预期(比如LIMIT 10前要扫描百万行) - 多表
JOIN后结果集膨胀,超出内存限制 -
SELECT *拉了大字段(如TEXT、BLOB),哪怕只 10 行也轻易突破 1GB 限制 - 并发查询多,每个线程都独占一份
tmp_table_size内存,总占用翻倍
如何快速定位是哪条 SQL 在疯狂生成临时表
别只盯 SHOW PROCESSLIST 里状态为 Copying to tmp table 的线程——它可能已经卡住几秒甚至几分钟,而罪魁祸首早被 kill 掉了。更有效的方式是开启性能分析:
- 执行
SET profiling = 1;,再复现慢查询 - 用
SHOW PROFILES;查最近查询耗时,找耗时长且状态含Copying to tmp table的 ID - 用
SHOW PROFILE FOR QUERY N;(N 是上一步 ID)看各阶段耗时,确认Copying to tmp table是否占主导 - 配合
SELECT * FROM information_schema.PROCESSLIST WHERE STATE = 'Copying to tmp table';抓实时活跃线程,记下ID和INFO字段内容
注意:profiling 是会话级开关,生产环境建议只在问题复现窗口内临时开启,避免性能开销。
tmp_table_size 和 max_heap_table_size 要设多大才合理
设太大浪费内存,太小频繁落盘。关键不是绝对值,而是和你最重的查询匹配:
- 查当前配置:
SHOW VARIABLES LIKE 'tmp_table_size';和SHOW VARIABLES LIKE 'max_heap_table_size'; - 两者取小值才是实际生效上限,所以必须同时调整,且建议设为相等
- 线上建议从
256M起步(而非默认的 16M),若单表JOIN后结果常超 50 万行,可试512M;但别盲目堆到 2G+,否则可能触发 OS OOM killer - 动态设置仅对新连接生效:
SET GLOBAL tmp_table_size = 268435456;,重启后失效;永久修改需写入/etc/my.cnf的[mysqld]段
特别注意:tmpdir 路径(通常是 /tmp)如果挂载在内存盘(tmpfs),落盘速度反而比普通磁盘还慢——务必用 df -h $(mysql -Nse "SELECT @@tmpdir;") 确认实际磁盘空间与类型。
哪些 SQL 写法会隐式强制用临时表
不是所有 GROUP BY 或 ORDER BY 都能走索引,以下写法极易中招:
-
ORDER BY多列方向不一致:ORDER BY a ASC, b DESC(5.7+ 支持部分优化,但多数场景仍需临时表) -
GROUP BY列含函数或表达式:GROUP BY DATE(created_at)、GROUP BY CONCAT(first_name, ' ', last_name) -
UNION各子句列类型不兼容,MySQL 会额外建临时表做类型对齐 -
SELECT DISTINCT+ORDER BY非 SELECT 列:SELECT DISTINCT name FROM t ORDER BY id(id不在 SELECT 中,无法利用索引排序)
改法很简单:把 ORDER BY 或 GROUP BY 的列单独建联合索引,且顺序、方向严格匹配;避免在 WHERE 或 SELECT 中对索引列做函数操作。
临时表本身不可怕,可怕的是它成了掩盖低效 SQL 的遮羞布。真正要砍掉的,是那个本不该扫百万行才出 10 条结果的 JOIN 条件,而不是给 tmp_table_size 再加 512MB。


















