结论是:undo log膨胀本质是长事务卡住purge线程导致历史版本无法清理,必须先终止trx_state='RUNNING'且trx_query IS NULL的最老事务,否则调参无效;定位需查INNODB_TRX按trx_started排序,结合PROCESSLIST确认异常连接;处理须分状态KILL,清理后需加大innodb_purge_batch_size并启用undo截断,根治须从应用层杜绝事务泄漏。

直接说结论:undo log 膨胀不是磁盘空间问题,是长事务卡住 purge 线程导致历史版本无法清理。不先杀掉最老的 trx_state = 'RUNNING' 且 trx_query IS NULL 的事务,调任何参数都白搭。
怎么快速定位真正卡住 purge 的长事务
别信 SHOW PROCESSLIST 的 Time 字段——它只算当前语句执行时长。真正钉死 undo 的,是那些已空闲但没提交的事务:
- 运行
SELECT trx_id, trx_started, trx_state, trx_rows_modified, trx_mysql_thread_id 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,查HOST、USER、COMMAND和INFO,确认是不是应用连接异常挂起(比如 Python 进程崩溃但连接未 close) -
trx_rows_modified = 0的只读事务也会阻塞 purge,但危害小;真正危险的是trx_rows_modified > 10000且长时间未提交的写事务
kill 前必须分状态处理,否则可能更糟
盲目 KILL 可能让实例雪上加霜:
-
trx_state = 'RUNNING'且trx_query IS NULL:极大概率是应用忘了COMMIT/ROLLBACK,可安全KILL -
trx_state = 'LOCK WAIT':它被别的事务堵住了,先查INNODB_LOCK_WAITS找出blocking_trx_id,优先干掉上游事务 -
trx_state = 'ROLLING BACK':别动,此时KILL会让回滚更慢、更占 IO,甚至拖垮 purge 线程 - 若
trx_rows_modified > 100000,回滚可能耗时数分钟,KILL前务必评估业务影响
purge 跟不上时,怎么临时加速清理
杀完源头后,HISTORY LIST LENGTH 不会立刻下降,得主动助推 purge:
- 先确认 purge 是否在推进:执行
SHOW ENGINE INNODB STATUS\G,找PURGE DONE for trx's n:o < XXX行,对比trx_id看是否在往前走 - 临时加大单次清理量:
SET GLOBAL innodb_purge_batch_size = 10000;(默认 300) - 提高 purge 频率:
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;(默认 128,值越小越激进,但 CPU 负载会上升) - 极端磁盘将满时可短时启用:
SET GLOBAL innodb_max_purge_lag = 0;(仅限 10 分钟内,否则 MVCC 读会大量触发Lock wait timeout exceeded)
独立 undo 表空间如何安全缩容
物理文件不收缩是正常现象,但只有独立表空间才能真正释放磁盘空间:
- 确认已启用独立 undo:
SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'innodb_undo_tablespaces';,值必须 ≥ 2(推荐 4) - 等
HISTORY LIST LENGTH降到 1000 以下(SHOW ENGINE INNODB STATUS\G查),再执行ALTER UNDO TABLESPACE undo_001 INACTIVE; - 查
SELECT NAME, STATE FROM information_schema.FILES WHERE SPACE_TYPE = 'Undo';,确认状态变为INACTIVE - 最后执行
DROP UNDO TABLESPACE undo_001;(MySQL 8.0.23+ 支持) - 严禁直接删物理文件,也不可在
ACTIVE状态下DROP,否则实例可能崩溃
最容易被忽略的一点:所有参数调整和 truncate 操作,都依赖 purge 能跟上。而 purge 跟不上的根因永远在应用层——比如事务里调 HTTP、批量更新不分页、连接池没正确 close()。数据库配置只是兜底,不是解药。


















