undo表空间暴涨本质是事务生命周期失控,须先定位并kill trx_state='running'且trx_query is null的空闲事务;再通过innodb_trx与processlist关联确认来源,分状态处理;最后加大purge_batch_size、启用自动截断并轮换undo表空间。

Undo表空间暴涨不是磁盘配额不够的问题,而是事务生命周期失控的直接结果——只要有一个 trx_state = 'RUNNING' 且 trx_query IS NULL 的事务挂着,purge 线程就彻底卡死,History list length 持续上涨,undo 文件只能单向膨胀。
怎么快速定位真正卡住 purge 的长事务
别信 SHOW PROCESSLIST 的 Time 字段,它只统计当前语句执行时长,和事务开启时间无关。真正钉死 undo 的,是那些早已空闲但没提交的连接。
- 运行这条语句查最老的可疑事务:
SELECT trx_id, trx_started, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec, trx_state, trx_rows_modified, trx_mysql_thread_id FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 600 ORDER BY trx_started LIMIT 5;
- 重点关注:
duration_sec > 600、trx_state = 'RUNNING'、且trx_query IS NULL的记录——99% 是应用崩溃后连接未 close,或 ORM 开了事务但漏了COMMIT -
trx_rows_modified > 10000且持续时间长的写事务必须立刻干预;trx_rows_modified = 0的只读事务虽不新增 undo,但也会阻塞 purge - 用
trx_mysql_thread_id关联information_schema.PROCESSLIST,查HOST、USER、COMMAND和INFO,确认是否为异常挂起(比如 Python 进程已退出但连接残留)
kill 前必须按 trx_state 分类处理
盲目 KILL 不仅无效,还可能让实例雪崩。不同状态对应完全不同的底层行为:
-
trx_state = 'RUNNING'且trx_query IS NULL:可安全KILL对应线程,大概率是连接泄漏或事务未关闭 -
trx_state = 'LOCK WAIT':说明它被别的事务堵住了,先查INNODB_LOCK_WAITS找出blocking_trx_id,优先干掉上游事务 -
trx_state = 'ROLLING BACK':千万别动!此时KILL会让回滚从同步变异步,IO 更爆、耗时翻倍,甚至拖垮 purge 线程 - 若
trx_rows_modified > 100000,回滚可能持续数分钟,KILL前务必评估业务影响;若History list length > 10000,批量KILL必须加SLEEP(0.2)间隔执行,避免 IO 冲突
杀完之后怎么让 History list length 真正回落
杀掉源头事务后,History list length 不会立刻下降——purge 线程需要主动助推,否则空间一直占着不放。
- 先检查 purge 是否在推进:
SHOW ENGINE INNODB STATUS\G,找PURGE DONE for trx's n:o 行,看数字是否持续增长,再比对刚 kill 掉的 <code>trx_id是否已被覆盖 - 临时加大清理能力:
SET GLOBAL innodb_purge_batch_size = 10000(默认 300) - 确保已启用独立 undo 表空间:
SHOW VARIABLES LIKE 'innodb_undo_tablespaces',值必须 ≥ 4(仅 ≥ 2 不够轮换) - 开启自动截断:
SET GLOBAL innodb_undo_log_truncate = ON;再执行ALTER UNDO TABLESPACE undo_001 SET INACTIVE→ 等History list length显著回落 →ALTER UNDO TABLESPACE undo_001 TRUNCATE
整个流程必须严格按顺序执行:先定位并分类 kill,再助推 purge,最后轮换截断。跳过任一环节,比如只调参不 kill、或没新增 undo 表空间就直接 TRUNCATE,都会失败。最常被忽略的是——你以为 purge 在跑,其实它早就停在某个 trx_id 上不动了,得靠 SHOW ENGINE INNODB STATUS 里的数字来验证,而不是靠感觉。











