临时表用磁盘而非内存是因为MySQL根据tmp_table_size和max_heap_table_size的较小值判断内存是否足够,超限即退化到磁盘;含BLOB/TEXT或字段定义过宽也会强制落盘。

临时表为什么会用磁盘而不是内存
MySQL 在执行 GROUP BY、ORDER BY、子查询或 UNION 时,如果中间结果集太大或含有 BLOB/TEXT 字段,会把临时表从内存(MEMORY 引擎)退化到磁盘(MyISAM 或 InnoDB),这时就看到 Created_tmp_disk_tables 指标飙升,I/O 开销明显增大。
根本原因不是“临时表本身慢”,而是 MySQL 判断内存不够用了——它看的是两个配置的较小值:tmp_table_size 和 max_heap_table_size。只要任一结果行超过这个阈值,哪怕只超 1 字节,整个临时表就落地。
-
tmp_table_size是 SESSION 级限制,可动态调;max_heap_table_size是 GLOBAL + SESSION 双层控制,且影响所有内存表(包括显式CREATE TEMPORARY TABLE ... ENGINE=MEMORY) - 如果应用里大量使用
VARCHAR(2000)或TEXT字段做排序,即使数据实际很短,MySQL 也会按定义长度预估内存占用,直接触发磁盘落盘 - 5.7+ 版本默认
tmp_table_size = 16M,但很多 OLAP 查询轻松突破,尤其带多字段GROUP BY时
怎么安全地调大 tmp_table_size
盲目设成 1G 不解决问题,反而可能耗尽内存、引发 OOM。关键是结合业务特征和监控反馈来调。
- 先查当前压力:运行
SHOW GLOBAL STATUS LIKE 'Created_tmp%';,重点关注Created_tmp_disk_tables / Created_tmp_tables比值,高于 10% 就值得优化 - 观察单次查询:开启
slow_query_log并设置log_queries_not_using_indexes = ON,配合long_query_time = 0抓出高频落地磁盘的 SQL - 调参建议:在内存充足前提下,把
tmp_table_size和max_heap_table_size设为相等,例如64M;不要超过物理内存的 10%~15%,避免挤占 InnoDB buffer pool - 改完必须验证:用
EXPLAIN FORMAT=JSON看目标 SQL 的used_temp_table和using_filesort是否减少,再比对Created_tmp_disk_tables增速
比调参数更有效的三种绕过方式
很多场景下,与其硬扛临时表大小,不如让 SQL 自己避开它。MySQL 对某些写法特别敏感,微调就能省掉整个临时表。
- 用覆盖索引替代
SELECT *:比如SELECT user_id FROM orders GROUP BY user_id如果user_id有索引,就不用临时表;加了ORDER BY create_time却没索引,立刻触发 - 拆分复杂
GROUP BY:含多个非聚合字段时(如GROUP BY a, b, c),尝试先GROUP BY a落临时表,再 JOIN 补字段,有时总开销反而更低 - 禁用排序临时表:如果业务允许结果无序,显式加
ORDER BY NULL,MySQL 会跳过排序阶段,避免因ORDER BY引发的额外临时表
tmp_table_size 对不同存储引擎的影响差异
别以为调大就万事大吉——InnoDB 临时表和 MEMORY 临时表行为完全不同,尤其在 8.0+ 版本。
- 8.0 默认用
InnoDB作为内部临时表引擎(由internal_tmp_mem_storage_engine控制),此时tmp_table_size仅控制内存阶段上限,超过后直接走InnoDB磁盘表,不经过MyISAM -
MEMORY引擎临时表不支持 BLOB/TEXT,一旦 SELECT 中出现这类字段,不管tmp_table_size多大,必然落地磁盘 - 如果强制设
internal_tmp_mem_storage_engine=MEMORY,遇到 TEXT 字段会报错ERROR 1163 (42000): The used table type doesn't support BLOB/TEXT columns,而不是静默降级
真正卡住性能的,往往不是参数值本身,而是你没意识到某条 SQL 因字段类型或缺失索引,根本没机会用上这个参数。










