长事务导致UndoLog暴涨,根本原因是其未提交而持续引用旧版本数据,阻塞purge线程清理,致使History list length升高、undo文件膨胀、iowait上升。

长事务为什么导致 UndoLog 暴涨
UndoLog 无法被 purge 线程清理,根本原因是它还在被某个活跃事务“引用”——只要事务没提交,所有已修改行的历史版本就必须保留,否则 MVCC 就会读到不一致的数据。
常见现象包括:History list length 持续升高、ibdata1 或独立 undo tablespace 文件不断膨胀、iowait 明显上升。这不是磁盘空间用完了才出问题,而是 purge 线程被卡住后,整个 InnoDB 的并发性能都会下滑。
-
innodb_undo_log_truncate=ON必须开启,否则即使设置了innodb_max_undo_log_size也不会触发截断 - 从库上
History list length特别高?先查information_schema.INNODB_TRX,大概率是主库某个长事务同步到了从库,但从库回放慢,导致 purge 更滞后 - 不要依赖
SHOW ENGINE INNODB STATUS查 long trx——它只显示最近几条;必须用information_schema.INNODB_TRX配合时间差计算,例如:WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 30
长事务如何延长行锁持有时间
InnoDB 不会把行锁“升级”成表锁,但长事务会让 next-key lock 一直挂着——哪怕你只 UPDATE 了一行,它连带锁住索引间隙,其他事务在相同范围做 INSERT 或 SELECT ... FOR UPDATE 就会被堵住,看起来像整张表被锁死。
更隐蔽的问题是:事务空闲等待(比如应用层拿到连接后没发 SQL,也没 COMMIT)也会持续持锁。此时 trx_state = 'RUNNING',但 trx_query IS NULL,说明语句早执行完了,只是连接没关、事务没结束。
- 连接池(如 HikariCP)中未显式控制事务边界,极易造成这种“假空闲真持锁”状态
-
TRX_ROWS_LOCKED和TRX_LOCK_STRUCTS在INNODB_TRX中持续增长,是锁资源被长期占用的直接信号 - 隔离级别为
REPEATABLE READ时,gap lock 范围更大,长事务下阻塞面更广;若业务允许,可评估降级到READ COMMITTED
kill thread 前必须确认的三件事
直接 KILL 看似解燃眉之急,但可能引发更严重后果:比如正在跑账务对账的事务被干掉,后续数据核对就全乱了。
- 查
trx_mysql_thread_id对应的trx_query是否为空——空值意味着它早已执行完,只是忘了 COMMIT,这类最该杀 - 用
performance_schema.threads关联查该线程的PROCESSLIST_INFO和CONNECTION_TYPE,确认是不是应用直连还是中间件发起 - 检查该线程是否出现在
replication_applier_status_by_coordinator中——如果是从库的 applier 线程,KILL可能中断复制流
真正有效的防御点不在数据库侧
所有靠定时脚本 KILL 或 DBA 人工巡检的方式,都是在给漏洞打补丁。真正稳的解法藏在应用代码里。
- 禁止跨 HTTP 请求/跨函数隐式延续事务;每个写操作必须包裹在明确的
BEGIN/COMMIT或等价框架注解(如 Spring 的@Transactional)内 - 在事务内避免远程调用、文件读写、复杂计算等非 DB 操作——这些会让锁持有时间不可控
- 监控要落到毫秒级:不是看“平均事务耗时”,而是抓
P95或阈值告警(比如 >30s 的事务立即推送告警)
最容易被忽略的是:事务开始时间(trx_started)和连接建立时间完全不是一回事,很多监控误把连接空闲当事务活跃,结果漏掉真凶。


















