必须先杀掉trx_state='RUNNING'且trx_query IS NULL的最老事务,否则调参、截断、加大purge_batch_size均无效;需用INNODB_TRX按trx_started排序定位,关联PROCESSLIST确认异常连接后分状态KILL,再助推purge并启用undo截断。

长事务本身不写undo log,但它会钉住已存在的undo log不让purge线程清理——这才是undo log膨胀的真正原因。所有调参、截断、加大batch_size都只是缓震,不先杀掉那个最老的空闲事务,其他操作全白搭。
查INNODB_TRX定位真凶,别信SHOW PROCESSLIST的Time字段
SHOW PROCESSLIST里的Time只反映当前语句执行时长,和事务开启时间无关。真正卡住purge的,是那些trx_state = 'RUNNING'且trx_query IS NULL的事务——它们早就空闲了,但没提交。
- 运行这条语句找最老的可疑事务:
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秒(10分钟)、trx_state = 'RUNNING'、且trx_query IS NULL的记录 - 用
trx_mysql_thread_id关联information_schema.PROCESSLIST,查HOST、USER、COMMAND和INFO,确认是否为异常挂起的应用连接(比如Python进程崩溃后连接未关闭)
KILL前必须分状态处理,乱杀会让IO更爆
KILL不是万能解药,不同trx_state对应完全不同的风险等级:
-
trx_state = 'RUNNING'且trx_query IS NULL:99%是应用漏了COMMIT或ROLLBACK,可安全KILL对应trx_mysql_thread_id -
trx_state = 'LOCK WAIT':它被别的事务堵住了,先查INNODB_LOCK_WAITS找出blocking_trx_id,优先干掉上游事务 -
trx_state = 'ROLLING BACK':千万别动!此时InnoDB正在同步回滚,KILL会让回滚变异步,IO更爆、purge更慢 - 若
trx_rows_modified > 100000,回滚可能持续数分钟,KILL前务必评估业务影响
杀完之后History list length不降?得手动助推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负载会上升) - 确保已启用独立undo表空间:
SHOW VARIABLES LIKE 'innodb_undo_tablespaces',值必须≥4(推荐)
最常被忽略的一点:即使杀掉了长事务、调大了purge参数,如果innodb_undo_log_truncate = OFF或undo表空间不足2个active,truncate机制根本不会触发——而MySQL 8.0要求至少保留2个active undo表空间才能轮换收缩,跳步必失败。


















