磁盘临时表的根本原因是MySQL被迫将临时表落盘,而非单纯使用临时表;必须同步设置tmp_table_size与max_heap_table_size为相同值(如256M),否则取较小值导致调参无效,且需监控Created_tmp_disk_tables增长及SQL是否含TEXT/BLOB等强制落盘因素。

直接结论:磁盘临时表不是“用了临时表”导致的,而是“被迫落盘”造成的——真正要盯的是 Created_tmp_disk_tables 持续增长,而不是 Created_tmp_tables 本身。
为什么调大 tmp_table_size 没用?
因为 MySQL 实际取 tmp_table_size 和 max_heap_table_size 的较小值作为内存上限。哪怕你把 tmp_table_size 设成 512M,只要 max_heap_table_size 还是默认的 16M,那临时表最多就只能用 16M 内存。线上环境必须让两者数值完全一致,否则调参等于白调。
- 检查当前值:
SHOW VARIABLES LIKE 'tmp_table_size';和SHOW VARIABLES LIKE 'max_heap_table_size'; - 设为相同值(例如 256M):
SET GLOBAL tmp_table_size = 268435456; SET GLOBAL max_heap_table_size = 268435456; - 注意权限:
SET GLOBAL需 SUPER 权限,且只影响新会话;重启后失效,需写入配置文件才持久 - 别盲目设太大:单连接可能独占这块内存,高并发下容易触发 OOM 或 swap,建议不超过物理内存的 10%~15%
哪些 SQL 一定会强制落盘,调参也救不了?
即使参数调得再大,以下情况仍会跳过内存、直奔磁盘:
- SELECT 中含
TEXT、BLOB、JSON字段——MEMORY引擎根本不支持这些类型 -
UNION各分支字段类型不一致,MySQL 做隐式转换后行宽膨胀,轻易超限 -
GROUP BY或ORDER BY字段是长VARCHAR(500)或未加索引的表达式(如UPPER(name)) - 查询里有
SELECT ... FOR UPDATE—— 内存表无法支持行级锁,自动降级为磁盘表
验证方式:执行完 SQL 后立刻查 SHOW STATUS LIKE 'Created_tmp%',如果 Created_tmp_disk_tables 增加了,说明已落盘。
怎么确认是临时表拖慢了查询?
别靠猜,看两个硬指标:
-
EXPLAIN输出中出现Using temporary—— 表示优化器决定建临时表,但不说明是否落盘 -
SHOW STATUS LIKE 'Created_tmp_disk_tables'在查询前后差值 > 0 —— 真正落到磁盘的证据 - 监控比值:
Created_tmp_disk_tables / Created_tmp_tables> 20% 就该干预;接近 0 则调参无意义 - 慢查询日志里若带
Copying to tmp table on disk状态,性能已受损
更细粒度定位可用 performance_schema.events_statements_summary_by_digest,查哪类 SQL 频繁触发磁盘临时表。
显式建临时表时最容易踩的坑
手动 CREATE TEMPORARY TABLE 不是万能解药,用错反而更慢:
- 别用
ENGINE=MEMORY:它不支持TEXT/BLOB,也不支持事务和行锁,出错概率高;优先选ENGINE=InnoDB - 字段类型必须精确控制:
CONCAT(a,b,c)可能推导出超宽VARCHAR(1024),哪怕实际值很短;显式指定宽度,比如VARCHAR(64) - 建完立刻加索引:没主键或唯一索引的临时表,后续
JOIN或WHERE就是全表扫描 - 避免
SELECT * INTO TEMPORARY TABLE:只选必要字段,压缩行宽,减少内存/磁盘压力
真正值得显式建临时表的场景很窄:比如报表中需多次引用同一中间聚合结果(超 10 万行),且后续要加索引加速;其他时候,优先优化原 SQL 或改用覆盖索引。
临时表性能问题的核心从来不在“要不要用”,而在于“能不能留在内存里”。参数只是兜底手段,SQL 结构、字段类型、索引覆盖才是决定性因素。最容易被忽略的一点是:TEXT 字段的存在,会让整个临时表直接放弃内存路径——哪怕你只 SELECT id, name,只要源表里有 TEXT 字段且被隐式包含(比如用 * 或派生表未明确裁剪),就可能触发落盘。



















