监控里看到 Created_tmp_disk_tables 很高,常见做法是立刻把 tmp_table_size 和 max_heap_table_size 调大。这个方向有时有效,却不是通用答案。计数可能只是数据库运行很久后的累计值,某些SQL因为结构和字段类型天然需要磁盘临时表,MySQL 8.0的TempTable机制也和5.7不同。
磁盘临时表本身不是错误。排序、聚合、去重、派生表和部分视图都可能需要内部临时表。真正的问题是创建速率突然升高、磁盘I/O和查询延迟同步恶化,或某些SQL长期制造大量可避免的临时数据。

先计算时间窗口内的速率和比例
读取全局状态:
SHOW GLOBAL STATUS LIKE 'Created_tmp_tables';
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';
SHOW GLOBAL STATUS LIKE 'Uptime';
这些是累计计数。间隔60秒采集两次,用差值计算每秒创建速率,再比较磁盘临时表与全部内部临时表的比例。只看启动以来的总量,会把历史高峰和当前状态混在一起。
还要注意,执行 SHOW STATUS 自身也会使用内部临时表并增加相关计数。监控频率过高会轻微干扰数据。MySQL 8.0默认TempTable溢出可能使用内存映射文件,Created_tmp_disk_tables 存在未完整覆盖这种路径的已知限制,因此该指标不能单独代表全部磁盘临时空间。
区分MySQL版本与临时表引擎
先记录版本和相关变量:
SELECT VERSION();
SHOW VARIABLES LIKE 'internal_tmp_mem_storage_engine';
SHOW VARIABLES LIKE 'tmp_table_size';
SHOW VARIABLES LIKE 'max_heap_table_size';
SHOW VARIABLES LIKE 'temptable_max_ram';
SHOW VARIABLES LIKE 'temptable_max_mmap';
SHOW VARIABLES LIKE 'tmpdir';
MySQL 5.7常以 tmp_table_size 与 max_heap_table_size 中较小值限制MEMORY内部临时表。MySQL 8.0默认使用TempTable引擎,较新版本中 tmp_table_size、temptable_max_ram 与 temptable_max_mmap 分别控制单表和全局资源边界。
不同小版本的默认值和行为会变化,MariaDB也不是完全相同的实现。不能把5.7教程中的参数组合原样套到8.0或MariaDB。
找出制造临时表的SQL
MySQL 8.0的sys库可直接列出使用临时表较多的语句摘要:
SELECT query, db, exec_count, memory_tmp_tables, disk_tmp_tables,
avg_tmp_tables_per_query, tmp_tables_to_disk_pct, total_latency
FROM sys.statements_with_temp_tables
ORDER BY disk_tmp_tables DESC
LIMIT 20;
语句已被规范化,适合先按摘要定位。还要结合 exec_count 判断是单次查询制造大量临时表,还是高频小查询累计形成压力。总量大但每次成本低,与单次报表查询产生数GB临时数据,处理方法不同。
Performance Schema摘要被重置、容量不足或未启用时,结果可能不完整。慢查询日志也可辅助定位,但启用与阈值调整会增加写盘和敏感SQL暴露风险,应在评估后进行。
用EXPLAIN确认临时表出现在哪一步
对候选SQL使用:
EXPLAIN FORMAT=JSON SELECT ...;
EXPLAIN ANALYZE SELECT ...;
EXPLAIN ANALYZE 会实际执行查询,生产环境中的写语句、超大查询或高负载时段不能随便运行。可先用普通EXPLAIN,再在脱敏副本验证实际耗时。
传统EXPLAIN的Extra出现 Using temporary,说明执行计划中某一步使用临时表,但并不覆盖所有内部物化场景。要继续检查 GROUP BY、ORDER BY、DISTINCT、UNION、派生表、CTE和窗口函数。
排序列与分组列不一致、连接顺序无法利用索引、返回列过宽,都可能扩大临时结果。优化目标不是机械消灭所有 Using temporary,而是减少不必要的数据量和磁盘转换。
检查字段类型与结果宽度
MySQL 5.7中包含BLOB或TEXT等字段的内部临时表更容易落盘。MySQL 8.0的TempTable对部分可变长与大对象支持有所改进,但大结果仍会触达单表或全局限制。
报表查询使用 SELECT *,即使最终只展示少量列,也会把宽字段带入排序和物化。先缩小选择列、提前过滤行数,并检查是否能用覆盖索引减少中间结果。
不要为了避免临时表把业务需要的TEXT字段随意改短。字段类型调整涉及数据完整性、索引和应用兼容,应通过SQL结构和访问路径先解决。
检查临时空间与磁盘I/O
内部临时数据落盘后,要确认实际目录和文件系统:
df -h
df -i
iostat -xz 1 10
MySQL 8.0的InnoDB会话临时表空间通常位于数据目录相关位置。可查询:
SELECT ID, SPACE, PATH, SIZE, STATE
FROM information_schema.INNODB_SESSION_TEMP_TABLESPACES
ORDER BY SIZE DESC;
该视图的可用字段和权限取决于版本。临时文件可能在创建后立即解除目录项,普通 ls 看不到,但空间仍由打开文件占用。磁盘告急时不要直接删除MySQL数据目录下不认识的临时表空间文件,应先确认官方管理方式和活跃会话。
是否应该调大tmp_table_size
调大阈值能让部分临时表留在内存,但并不保证所有查询都使用内存,也不一定更快。多个并发查询同时创建大临时表,会放大数据库内存压力,可能挤压InnoDB缓冲池或触发Swap与OOM。
调整前应估算并发临时表数量、单表大小、全局TempTable限制和服务器可用内存。MySQL 8.0.28之后,tmp_table_size 对TempTable单个内存临时表的限制更明确;旧版本和MEMORY引擎的关系不同。
临时动态调整只适合受控验证,确定结果后再使用符合当前安装方式的持久化配置。不要同时增大多个内存参数并重启数据库,否则无法确认收益和风险。
优先从SQL和索引减少中间数据
优化顺序通常是先定位SQL,再减少参与排序、分组和去重的行与列。把过滤条件提前、建立符合查询顺序的联合索引、移除不必要的 DISTINCT、用 UNION ALL 取代无需去重的 UNION,都可能减少临时结果。
每项修改都要通过执行计划和真实数据分布验证。索引过多会增加写入成本,改变SQL还可能影响结果语义。不能为了让监控指标好看而牺牲正确性。
当生产数据不适合直接试验,可以在 萤光云 建立相同MySQL版本的脱敏副本,或在按小时计费的 LightNode 上用合成数据做执行计划与并发对照。数据规模和分布应接近生产,但不能带入真实用户信息。
修复后怎样验收
验收至少跨过一个真实业务高峰,按相同采样间隔比较:
SHOW GLOBAL STATUS LIKE 'Created_tmp_tables';
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';
同时观察候选SQL的执行次数、延迟、临时表数量、数据库内存、磁盘吞吐与业务P95、P99。磁盘临时表比例下降但内存压力和延迟上升,不算成功。
完整的修复应能指出哪些SQL产生临时表、为何落盘、修改后速率和延迟如何变化,并证明内存没有被新的参数配置推向危险区。
常见问题
Created_tmp_disk_tables很大就代表现在很慢吗?
不代表。它是累计值,应计算时间窗口差值并结合查询延迟与磁盘I/O。
把tmp_table_size设得越大越好吗?
不是。并发查询可能同时占用更多内存,还可能挤压数据库其他缓存。需要按版本、并发和内存预算验证。
Using temporary一定要优化掉吗?
不一定。某些聚合、排序和物化本来就需要临时表。重点是执行成本、数据量和是否发生可避免的磁盘转换。


