Undo Log膨胀不收缩99%因purge被卡住,须先查INNODB_TRX定位trx_state='RUNNING'且trx_query IS NULL、trx_started超600秒的最老事务,再确认HISTORY LIST LENGTH是否停滞;innodb_undo_log_truncate=ON仅是允许截断开关,生效需同时满足innodb_undo_tablespaces≥2、HISTORY LIST LENGTH<500、purge已推进。

undo log 膨胀不收缩,99% 是因为 purge 被卡住,而不是配置没开 —— 先查 INNODB_TRX 找最老的 trx_state = 'RUNNING' 且 trx_query IS NULL 的事务,再看 HISTORY LIST LENGTH 是否停滞不降。
怎么一眼识别真正卡住 purge 的长事务
别信 SHOW PROCESSLIST 的 Time 字段,它只统计当前语句执行时长。真正钉死 undo 回收的是那些“空闲但没提交”的事务:
- 运行
SELECT trx_id, trx_started, trx_state, trx_rows_modified, trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5;,重点关注trx_started超过 600 秒、trx_state = 'RUNNING'、且trx_query IS NULL的记录 - 用返回的
trx_mysql_thread_id关联information_schema.PROCESSLIST,确认是不是应用连接异常挂起(比如 Java 连接池未 close、Python 进程崩溃但 TCP 连接未断) -
trx_rows_modified > 10000且持续超 300 秒的写事务,必须立刻干预;只读事务虽危害小,但若trx_started极老,也会拖慢 purge
为什么 innodb_undo_log_truncate=ON 却没反应
这个参数只是“允许截断”,不是“立即截断”。它是否生效,取决于三个硬性条件是否同时满足:
-
innodb_undo_tablespaces必须 ≥ 2(推荐设为 4),否则 MySQL 根本不会进入轮换逻辑;如果查出来是 0 或 1,说明 undo 还在ibdata1里,innodb_undo_log_truncate完全不参与流程 -
HISTORY LIST LENGTH必须降到 500 以下(越低越稳),查法:SHOW ENGINE INNODB STATUS\G,搜索HISTORY LIST LENGTH行 -
innodb_purge_rseg_truncate_frequency默认 128,意味着 purge 每处理 128 个事务才检查一次能否截断;高负载下可能几小时才轮到一次,可临时设为16加速
如何验证 truncate 是否真被触发
日志才是唯一可信依据。不要看文件大小,也不要看变量值:
- 执行
ALTER UNDO TABLESPACE undo_001 SET INACTIVE后,等 1–2 分钟,查 MySQL 错误日志:grep "Truncating tablespace" /var/log/mysql/error.log - 出现类似
[Note] InnoDB: Truncating tablespace ./undo/undo_001才算真正执行了逻辑截断(重置 LSN、清空页位图) - 此时
du -h undo_001.ibu大小不变是完全正常的 —— 截断不调ftruncate(),也不删文件,只是让内部空间可复用
物理文件大小不降,还能怎么办
想让 du -sh 数值下降,只有两条路,且版本强相关:
- MySQL 8.0.30+:执行
OPTIMIZE TABLESPACE undo_001(需表空间状态为active且 purge 已完成),部分版本能回收内部碎片、小幅减小物理尺寸 - 低于 8.0.30:停库 → 确保
innodb_fast_shutdown = 0→ 删除undo_001.ibu→ 重启,MySQL 会重建一个初始大小(约 10–16MB)的新文件 - 注意:删除前必须确认该表空间已
SET INACTIVE且状态为INACTIVE,否则实例启动失败报Cannot find undo tablespace
真正容易被忽略的点是:你看到的“膨胀”,往往不是单个 undo 文件的问题,而是 purge 长期被卡住后,多个 undo 表空间轮着追加写入的结果。不先清理源头事务,后续所有操作都是在给一个漏水的桶反复擦水渍。


















