资讯动态

MySQL临时表‘is full‘报错深度解析:原理、排查与根治方案

发布时间:2026/10/21 7:30:47 来源:尧图企业网站定制
看到 MySQL 客户端抛出The table /mysqldata/mysqltmp/#sql482e_2f6f60_3b is full!这个报错大多数人的第一反应是磁盘满了赶紧去清临时文件。实际上这句话的真正含义是MySQL 在执行 SQL 时生成的内部临时表写不进磁盘了。临时表文件被创建在 tmpdir 指向的目录下而那个目录所在的文件系统已经无法再写入。报错背后不只是磁盘空间不足还有 inode 耗尽、内存临时表大小上限、TEXT/BLOB 字段强制落盘等一连串机制问题。这篇文章会从临时表的生命周期讲起把这张报错背后的原理、排查方法和处理方案完整拆一遍。不管你是 DBA、后端开发还是刚接手数据库运维的新手这套思路可以直接拿去用。1. 报错机制拆解临时表是怎么被撑满的1.1 MySQL 内部临时表的完整生命周期先搞清楚报错里那个 #sql 文件是什么。MySQL 在执行排序、去重、分组、UNION 这类操作时如果内存装不下中间结果就会在磁盘上生成一张“内部临时表”。文件名以#sql开头后面跟着表 ID 和一串随机哈希文件就放在 tmpdir 指定的目录下。你看到的/mysqldata/mysqltmp/就是这台机器专门为临时表准备的目录。内部临时表的产生路径一般分两步。第一步MySQL 会优先在内存里用 MEMORY 存储引擎创建临时表这时的大小上限由tmp_table_size和max_heap_table_size中较小的那个决定常见默认值都是 16MB。第二步当中间结果超过这个上限或者临时表中包含 MEMORY 引擎无法支持的数据类型MySQL 就把临时表“降级”到磁盘上向 tmpdir 目录写文件。SQL 执行完、会话关闭时临时表文件会被自动删除。这个流程里有几个容易忽略的点。一是“内存临时表上限”是每张临时表单独计算的不是整个会话共享 16MB一个复杂 SQL 完全可能同时产生多张内部临时表内存使用会成倍叠加。二是用户用CREATE TEMPORARY TABLE显式创建的临时表同样受tmp_table_size和max_heap_table_size约束只是文件形态不同。三是如果 MySQL 进程被强杀或者机器断电临时表文件不会自动清理下次启动后 tmpdir 下可能残留大量 #sql 文件这些残留文件本身也会占用空间。1.2 为什么“is full”不只有一种原因MySQL 报The table ... is full是一个比较笼统的错误源码层面就是“写临时表失败了”但导致失败的具体原因可以细分。我整理了一个对照表排查时直接按表逐行排除。可能原因具体表现判断手法tmpdir 所在分区写满df -h 显示使用率 100%看挂载点空间inode 耗尽磁盘有空间但无法创建新文件df -i 使用率 100%内存临时表达到上限MEMORY 引擎表写满报 table is full调整 tmp_table_size/max_heap_table_size 后恢复MyISAM 磁盘临时表达到最大行数临时表行数超 max_rows 限制少见一般伴随超大结果集TEXT/BLOB 列强制落盘未到内存上限就写磁盘磁盘又不足检查 SQL 是否包含大字段实际操作中前两项占到九成以上。但有个细节经常被忽略df -h有空间df -i却可能满了。临时表文件通常体量小、数量多一个 tmp 目录下几万个 #sql 文件完全可能所以 inode 耗尽在临时表报错里非常常见。我见过一次现场分区还剩 20GB但 inode 使用率 100%MySQL 同样报 is full当时排查了好久才定位到。1.3 哪些 SQL 最容易引爆临时表不是所有查询都会产生临时表但下面这几种写法的触发概率极高大表上无索引的 GROUP BY 和 ORDER BYSELECT 列表里有大字段TEXT/BLOB还要排序或去重UNION 用的太多多表 JOIN 时驱动表顺序不对、ON 条件字段无索引子查询被优化器选择“物化”生成派生表再加上深分页比如LIMIT 1000000, 20。这些场景凑在一起临时表很可能直接落到磁盘。我遇到过一个典型现场某报表查询对一张千万级订单表做 GROUP BY 并计算 SUM再加 ORDER BY 排序命中量 300 万行。由于 GROUP BY 字段没有索引MySQL 先用内存临时表累积超过 16MB 后转磁盘。磁盘临时表写入持续几十秒期间 tmp 目录被撑到了 10GB。虽然最后 SQL 跑完了但同一时刻其他需要写临时表的会话全部开始报 is full整个应用直接瘫痪。这类问题往往不是慢 SQL 本身引发的而是“慢 SQL 占满了 temp 空间连累了所有正常查询”。所以处理时的优先级和普通慢查询完全不同先保住临时表目录的可用性再去优化那个 SQL。2. 实战排查把现场信息一次收全排查这类故障最忌讳只盯着报错路径删文件。MySQL 已经把问题范围缩得很小了就在 tmpdir 指向的那个目录里。接下来要做的是在一分钟内确认是目录所在分区的问题还是 MySQL 自身参数的问题还是某个 SQL 的问题。2.1 文件系统层面空间和 inode 都要看第一组命令永远先执行df -hT /mysqldata/mysqltmp df -i /mysqldata/mysqltmp du -sh /mysqldata/mysqltmp ls -lh /mysqldata/mysqltmp | head -20df -hT看分区剩余空间df -i看 inode这两个输出要同时分析。如果空间使用率接近 100%原因基本坐实如果空间还有剩余但 inode 满了看IUsed和IUse%。du -sh用来观察 tmpdir 目录当前到底占了多少能帮你判断有没有历史遗留的残留文件。ls输出里重点关注 #sql 开头、时间戳比较旧的记录。正常运行时活跃的临时表文件生命期很短查询一结束文件就没了。如果大量文件的时间戳是几天前的说明当时有会话异常终止留了一堆僵尸文件。注意只有在确认对应会话已不存在、MySQL 本身没有在使用这些文件时才适合清理这一点后面会展开说。2.2 从 MySQL 参数与状态确认临时表水位文件系统只是表象下一步要确认 MySQL 对临时表的容忍度设置了多少。执行SHOW VARIABLES LIKE tmpdir; SHOW VARIABLES LIKE internal_tmp_disk_storage_engine; SHOW VARIABLES LIKE tmp_table_size; SHOW VARIABLES LIKE max_heap_table_size;这里重点解释两个参数。tmp_table_size和max_heap_table_size不是相加而是取较小值作为内存临时表的实际上限。如果这台机器把tmp_table_size提到 128MB但max_heap_table_size还是默认 16MB内存临时表的上限依然是 16MB调了等于没调。这个“取较小值”的机制是排查时最容易被误会的点。再看全局临时表使用情况SHOW GLOBAL STATUS LIKE Created_tmp%;输出里有两个关键计数Created_tmp_tables表示执行语句时创建的临时表总数包含内存和磁盘Created_tmp_disk_tables表示其中落到磁盘的数量。正常情况下磁盘临时表占比应当很低比如 5% 到 20%。如果两个数字非常接近或者磁盘临时表数量在短时间内暴增说明要么临时表参数设置太小要么有 SQL 在大量触发磁盘临时表。2.3 从会话状态里揪出“肇事 SQL”定位具体 SQL 的方法有三种按优先级来。第一种是看当前正在执行的会话状态SHOW FULL PROCESSLIST;关注 State 列里出现Copy to tmp table或Creating sort index的会话这些大概率就在写临时表。结合连接来源、执行时间和 Info 里的 SQL 片段基本能锁定目标。确认后可以用KILL QUERY 会话ID先终止语句如果是 DML 要谨慎评估业务影响。第二种是看慢查询日志。临时表写满往往是持续了一段时间的过程慢日志里通常能捞到执行时间远超正常的语句。第三种是通过 performance_schema 的语句汇总表按创建磁盘临时表的次数排序找出高频触发者SELECT DIGEST_TEXT, SUM_CREATED_TMP_DISK_TABLES, COUNT_STAR FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_CREATED_TMP_DISK_TABLES DESC LIMIT 10;这三种方式各有利弊。实时会话适合故障正在发生时慢日志适合事后排查语句汇总表适合找规律性诱因。我的建议是三管齐下前两分钟就能把“谁的问题”定性清楚。3. 应急处理与根治方案排查结束后要分两步走先止血再根治。止血的核心是保可用性别让临时表目录继续被撑爆根治的核心是让同类 SQL 不再产生大量磁盘临时表或者让 tmp 目录有足够冗余。3.1 应急阶段先杀会话再考虑清文件如果确认有会话正在疯狂写临时表、且 tmp 目录快被占满第一步应该把这个会话停掉。建议先执行KILL QUERY 会话ID这样只是终止当前语句连接还保留如果语句不响应再用KILL 会话ID断开连接。会话结束后对应的临时表文件会由 MySQL 自动删除空间立刻释放。这里有个反直觉的坑不要直接去 rm 正在被 MySQL 使用的 #sql 文件。就算文件被删了MySQL 还在往那个文件句柄里写数据磁盘空间不会增加反而可能因为中途写入失败引发更复杂的错误。万一已经手动删了可以等这个会话结束后再看空间是否释放如果空间没有释放需要找到仍持有句柄的进程确认。对于已经确定的残留文件也建议谨慎处理。最安全的方式是确认 MySQL 当前没有活动会话在写临时表把这些文件移动到别的目录观察而不是直接 rm防止误删。如果残留文件非常多可以规划一个维护窗口短暂停库后在启动前清理 tmpdir这是最彻底也最安全的做法。3.2 参数调整让临时表尽量留在内存临时表爆盘的根源之一是内存上限设得太低。常见的调整方式是把tmp_table_size和max_heap_table_size一起上调例如从 16MB 调到 64MB 或 128MB。注意两个参数要一起改齐因为上面说过实际上限取两者较小值。但也不建议盲目调大内存临时表是会话级消耗并发 100 个会话同时建大临时表内存增长是成倍叠加的很可能把数据库实例内存榨干。另一个参数是internal_tmp_disk_storage_engineMySQL 5.7 里可以指定内部磁盘临时表用 MyISAM 还是 InnoDB。如果当前是 MyISAM每个临时表就是一个独立的 .MYD 文件大量并发会把 tmp 目录塞满小文件很快耗尽 inode改成 InnoDB 后临时表走共享的临时表空间文件数量大幅减少对 inode 更友好。MySQL 8.0 已经移除了 MyISAM 内部临时表不用再考虑这个问题。tmpdir 本身也可以优化。如果机器有多块磁盘可以把 tmpdir 配成多个目录用冒号分隔MySQL 会按照轮询方式把临时表分散到不同文件系统上等于同时做 IO 和容量的分摊。不过要特别注意只要其中一个目录满了MySQL 同样会报 is full。所以多目录方案解决的是均摊问题不能替代容量监控。3.3 SQL 优化从源头减少临时表参数调整是兜底SQL 优化才是根治。先看 GROUP BY 和 ORDER BY。如果这两类操作的字段上有合适的索引优化器可以直接按索引顺序扫描跳过 filesort 和临时表。注意复合索引要符合最左前缀原则比如GROUP BY a, b需要(a, b)这样的索引只有 a 的索引救不了 b。再看几个常见痛点。DISTINCT 去重如果业务上允许改成 GROUP BY 或者干脆去掉UNION 默认会去重必须去重时再用 UNION否则一律用 UNION ALL能省一张临时表深分页改写为“延迟 JOIN”先查出主键范围再关联原表取行避免在大结果集上 ORDER BY。子查询物化也值得关注生产经验里很多派生表临时表可以通过改写 JOIN 消除。还有一个容易忽略的点TEXT/BLOB 字段。只要临时表里需要容纳这类大字段MySQL 会直接创建磁盘临时表tmp_table_size调多大都没用。如果业务确实需要对包含大字段的数据排序去重建议把大字段拆到附属表查询时先过滤出主键集再取大字段让排序过程发生在不含 TEXT/BLOB 的临时表上。3.4 监控与容量规划处理完一轮故障必须把监控补上否则同类问题会以各种形态复发。基础监控至少包括 tmpdir 所在分区的空间使用率和 inode 使用率建议使用率到 80% 开始告警90% 触发紧急处理。数据库侧要盯住Created_tmp_disk_tables的增量变化这个指标一旦出现持续上升趋势说明 SQL 侧已经开始大量触发磁盘临时表。容量规划上tmpdir 建议使用独立分区避免和数据库数据目录争用空间。容量预估不能只看平时要看业务高峰期的临时表峰值用量比如报表跑批、大促等场景。一个取巧的办法是先持续收集一周的 tmp 目录占用曲线取峰值的 1.5 倍到 2 倍作为容量基准既不过度浪费也留有缓冲。4. 常见问题速查与避坑记录把散落的排查经验整理成速查表下次遇到直接按图索骥。我整理了几个最常见现象、定位方向和处理动作可以直接贴在运维文档里。4.1 问题与对策对照表现场现象优先定位方向处理动作df -h 分区 100%磁盘写满清理大文件、扩容、kill 大量写临时表的会话df -h 有空间但报 is fullinode 或参数限制执行 df -i清理残留小文件调整临时表引擎Created_tmp_disk_tables 占比高SQL 触发落盘多优化排序分组 SQL增大 tmp_table_sizetmpdir 下大量残留 #sql 文件历史异常退出查会话是否存活规划停机清理调了 tmp_table_size 没效果max_heap_table_size 未同步两个参数一起调取较小值生效SQL 含 TEXT/BLOB 仍落盘大字段强制磁盘临时表拆分大字段避免临时表承载大字段这张表建议收藏起来。尤其“tmp_table_size 调了没用”这一行我见过三次现场故障DBA 只调了 tmp_table_sizemax_heap_table_size保持在 16MB最后看着参数一头雾水。4.2 亲测踩过的坑第一个坑是把 tmpdir 指向内存盘。有人图读写快把 tmpdir 挂到了内存文件系统结果跑报表时内存和临时表一起爆MySQL 和操作系统同时告警。临时目录放在内存盘没问题但必须认真评估内存总量和临时表峰值否则就是拿系统稳定性换 IO 速度。第二个坑是误删正在使用的临时文件。当时磁盘告警我看到一个 8GB 的 #sql 文件就删了结果空间纹丝不动后来才发现那个会话还活着。正确顺序永远是先看会话状态再判断文件能不能清理。推荐用lsof L1查已删除但仍被占用的文件能看到是不是有进程还握着文件句柄。第三个坑是排查时只盯 MySQL 参数忽略了文件系统。一次现场所有 MySQL 参数都正常tmp 目录也只有几百 MB但df -i显示 inode 用完后来发现是大量历史残留的小文件把 inode 吃光了。所以每次排查临时表类报错df -h和df -i必须在同一轮完成不要分开看。第四个坑发生在清理残留文件时。停库清理前忘了检查复制拓扑中的从库状态导致主从临时目录不一致启动后同步进程报错。虽然这不是致命故障但提醒了一点操作 tmpdir 目录之前最好先看一眼拓扑状态做任何文件层面的清理都要有预案。4.3 一套顺手的一键巡检命令最后放一个我常用的巡检组合适合故障后例行检查或日常值班按顺序执行即可。命令都很简单目的是把空间、inode、临时表计数和运行中会话一次列全。# 1) tmpdir 空间和 inode df -hT /mysqldata/mysqltmp df -i /mysqldata/mysqltmp du -sh /mysqldata/mysqltmp # 2) MySQL 当前临时表参数 mysql -e SHOW VARIABLES LIKE tmpdir; SHOW VARIABLES LIKE internal_tmp_disk_storage_engine; SHOW VARIABLES LIKE tmp_table_size; SHOW VARIABLES LIKE max_heap_table_size; # 3) 临时表创建统计 mysql -e SHOW GLOBAL STATUS LIKE Created_tmp%; # 4) 正在写临时表的会话 mysql -e SHOW FULL PROCESSLIST;这组命令两分钟内能跑完足够覆盖故障现场的基本要素。日常巡检建议加个脚本留痕把 df、du、MySQL 状态输出追加到日志形成时间趋势后再做容量评估就有据可依了。最后分享一点我自己的排查习惯。看到The table /mysqldata/mysqltmp/#sql... is full这种报错其实 MySQL 已经给了两条关键线索路径指向 tmpdir文件名以 #sql 开头说明是内部临时表。所以我的动作顺序固定是先df -h和df -i看文件系统再SHOW GLOBAL STATUS看临时表落盘比例最后才去翻慢日志或会话状态定位 SQL。先环境、再参数、后代码这样不会漏判断也不容易被某一个表象带偏。处理完之后把慢查询和临时表指标留个档下次再看到 #sql 文件暴涨你就能在业务方找上门之前先把问题按死在 SQL 层面。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑