必须先查innodb_trx定位trx_state='running'且trx_query is null的事务,否则所有后续操作均无效;undo log膨胀本质是purge线程被卡死,非磁盘配额问题,需用指定sql查出超600秒空闲事务,关联processlist确认身份后分状态kill,并验证purge进度及启用undo截断。

必须先查 INNODB_TRX 定位 trx_state = 'RUNNING' 且 trx_query IS NULL 的事务,否则所有后续操作都是无效补救——Undo Log 膨胀本质是 purge 线程被卡死,不是磁盘配额问题。
怎么用 INNODB_TRX 快速揪出真正卡住 purge 的事务
别信 SHOW PROCESSLIST 的 Time 字段,它只统计当前语句执行时长,和事务开启时间完全无关。真正钉住 undo 的,是那些早已空闲、没发任何 SQL 却始终没 COMMIT 或 ROLLBACK 的连接。
- 运行这条语句定位最老的几个可疑事务:
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% 是应用崩溃后连接残留,或 ORM 开了事务但漏了commit -
trx_rows_modified > 10000且duration_sec > 300:大写事务已卡住,回滚可能持续数分钟 -
trx_rows_modified = 0但duration_sec > 3600:只读长事务也会阻塞 purge,只是危害稍小
-
- 用
trx_mysql_thread_id关联information_schema.PROCESSLIST,确认HOST、USER、INFO,避免误杀定时任务或报表导出作业
为什么不能直接 KILL 所有 long-running 线程
KILL 不是清理动作,而是高风险干预。不同 trx_state 对应完全不同的底层行为,乱杀会让 purge 更慢、IO 更爆,甚至拖垮实例。
-
trx_state = 'RUNNING'且trx_query IS NULL:可安全KILL对应线程,大概率是连接泄漏 -
trx_state = 'LOCK WAIT':它正被别的事务堵着,先查INNODB_LOCK_WAITS找出blocking_trx_id,优先干掉上游 -
trx_state = 'ROLLING BACK':绝对不要KILL,此时 InnoDB 正在同步回滚,KILL会强制转为异步,耗时翻倍、IO 暴涨 - 若
trx_rows_modified > 100000,回滚可能持续数分钟,KILL前务必确认业务是否允许中断 - 若
History list length > 10000,批量KILL必须加SLEEP(0.2)间隔执行,否则瞬间 IO 冲突可能引发雪崩
杀完事务后 History list length 为什么还不下降
杀掉源头事务不等于 undo 空间立刻回收。History list length 不下降,说明 purge 线程根本没推进——这时候光调参数没用,得先确认它是不是被卡死了。
- 执行
SHOW ENGINE INNODB STATUS\G,搜PURGE DONE for trx's n:o行,看数字是否在往前走;再比对刚KILL掉的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,但仅限应急,不可长期开启
真正容易被忽略的是:即使你把所有可疑事务都杀了,如果 innodb_undo_log_truncate = OFF 或未启用独立 undo 表空间(innodb_undo_tablespaces ),undo 文件仍不会缩容——MySQL 只会在满足轮换条件后才触发自动截断,跳过这一步,前面所有操作只是白忙。











