MySQL出现Copying to tmp table on disk的原因
时间:2026-07-03 07:06
“Copying to tmp table on disk” 的根源:内存临时表容量不足被迫写入磁盘 MySQL 并非自愿将数据写入磁盘,而是被迫进行溢出处理。当查询产生的中间结果——例如 GROUP BY 的聚合行、ORDER BY 的排序数据、多表 JOIN 的笛卡尔积——超过 tmp_
### “Copying to tmp table on disk” 的根源:内存临时表容量不足被迫写入磁盘
MySQL 并非自愿将数据写入磁盘,而是被迫进行溢出处理。当查询产生的中间结果——例如 GROUP BY 的聚合行、ORDER BY 的排序数据、多表 JOIN 的笛卡尔积——超过
tmp_table_size 与
max_heap_table_size 两者中的较小值时,原本存放于 MEMORY 引擎的临时表就会被转换为 MyISAM 或 InnoDB 类型的磁盘临时表。这一转换操作在状态中体现为
Copying to tmp table on disk。
关键点在于:
-
EXPLAIN 无法直接体现该状态,因为它仅预估执行计划,并不模拟内存压力场景
- 即使最终只返回 10 行数据,如果 SELECT 中包含了
TEXT、
BLOB 或超长
VARCHAR 等大字段,MySQL 会按其定义的最大长度进行内存估算,极易导致临时表溢出到磁盘
- 每个数据库连接独立占用一份
tmp_table_size 内存,在高并发场景下总内存消耗成倍增长,实际可用的临时表内存远低于单次配置值

### 最容易触发磁盘临时表的 SQL 模式
并非所有的 GROUP BY 或 ORDER BY 子句都会导致磁盘临时表,但以下 SQL 组合几乎必然触发溢出:
- 缺少索引的
ORDER BY 字段配合大偏移量的
LIMIT(例如
LIMIT 100000, 20),排序需要大量内存
-
GROUP BY 字段与 WHERE 条件中的列类型不匹配(例如
WHERE user_id = '123' 而对应列为
BIGINT),引发隐式类型转换,致使索引失效
- 多表 JOIN 之后再进行
GROUP BY 或
ORDER BY,且关联字段缺少联合索引覆盖(例如
ON t.a = r.a WHERE r.status = 1 ORDER BY t.updated_at,缺少
r(a, status) 和
t(updated_at) 索引),导致临时表无法在内部分组排序
-
SELECT * 配合
ORDER BY 会将所有大字段带入排序操作,内存瞬间被耗尽
### 如何确认磁盘临时表正在拖慢查询性能
不要仅凭
SHOW PROCESSLIST 中显示的卡住状态来判断,它可能只是表象。更准确的诊断方法是通过实时指标进行验证:
- 运行
SHOW GLOBAL STATUS LIKE 'Created_tmp%',并重点关注
Created_tmp_disk_tables 在查询执行前后的增量变化
- 通过
SET profiling = 1 启用会话性能分析,执行查询后查看
SHOW PROFILE FOR QUERY N,验证
Copying to tmp table on disk 阶段的耗时百分比是否超过 50%
- 结合
SELECT * FROM information_schema.PROCESSLIST WHERE STATE = 'Copying to tmp table on disk' 查询,记录
ID 和
INFO,进而反向定位具体的 SQL 语句
### 盲目调大 tmp_table_size 并非最佳策略
将
tmp_table_size 设置为 512M 甚至 1G 固然能暂时缓解症状,但会带来以下代价:
- 服务器内存充裕并不代表 MySQL 能够稳定运行——
max_heap_table_size 同时控制着 MEMORY 引擎表的上限,设置过大可能干扰其他功能的正常运作
- 当多个慢查询并发执行时,系统 OOM killer 可能在深夜直接终止 mysqld 进程
- 根本问题并未得到解决:一条缺少索引的 JOIN 语句,即使将参数调到 2G,最终依然会溢出到磁盘,只是时间推迟而已
- 正确的优化顺序应当是:优先添加合适的索引(覆盖 JOIN、WHERE、GROUP BY、ORDER BY 涉及的字段),然后精简 SELECT 字段列表,最后才考虑适度调整参数
有一个容易被忽视的关键点:
tmp_table_size 与
max_heap_table_size 必须设置为一致的值才能确保安全。因为实际生效的是两者中的较小值,单独调整其中一个等于徒劳。