用心打造
VPS知识分享网站

MySQL临时表写入磁盘过多怎么办?内存与SQL排查

监控里看到 Created_tmp_disk_tables 很高,常见做法是立刻把 tmp_table_sizemax_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_sizemax_heap_table_size 中较小值限制MEMORY内部临时表。MySQL 8.0默认使用TempTable引擎,较新版本中 tmp_table_sizetemptable_max_ramtemptable_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 BYORDER BYDISTINCTUNION、派生表、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一定要优化掉吗?

不一定。某些聚合、排序和物化本来就需要临时表。重点是执行成本、数据量和是否发生可避免的磁盘转换。

赞(0)
未经允许不得转载;国外VPS测评网 » MySQL临时表写入磁盘过多怎么办?内存与SQL排查
分享到