长事务阻塞Undo Log回收:因未提交事务钉住最老Read View,导致purge线程无法清理早于该视图的Undo日志,引发Undo表空间持续膨胀、回滚耗时剧增及锁等待加剧。

长事务阻塞Undo Log空间回收
Undo Log不是用完就删的,InnoDB靠purge线程异步清理已提交事务的旧版本数据。但只要有一个活跃事务(哪怕只执行了一条SELECT)没结束,它能看到的最老Read View就会一直保留——所有早于这个Read View的Undo Log版本都不能被purge回收。
典型表现是:information_schema.INNODB_TRX里看到TRX_STARTED时间极早的事务,同时innodb_undo_tablespaces对应文件持续增长,SHOW ENGINE INNODB STATUS中PURGE DONE FOR TRX停滞不前。
大事务写入直接生成海量Undo记录
每条INSERT/UPDATE/DELETE在事务内执行时,InnoDB都会为原始行生成一条Undo Log记录。10万行批量插入=10万条Undo Log;若事务未提交,这些日志全部滞留在Undo表空间,且无法复用或截断。
关键点:
-
innodb_undo_log_truncate在MySQL 5.7+默认开启,但仅对inactive的Undo表空间生效,长事务会让整个Undo空间长期处于active状态 - 单个Undo表空间文件(如
undo001)会不断追加,直到达到innodb_max_undo_log_size上限才尝试truncate,而长事务会卡住这个过程 - 如果使用
innodb_undo_tablespaces = 0(即系统表空间存放Undo),膨胀会直接拖垮ibdata1,且无法收缩
只读长事务同样危险
很多人误以为只有写事务才产Undo Log,其实只读事务只要开启得早、持续时间长,一样会钉住Read View,导致其他事务产生的Undo Log无法清理。比如一个后台报表查询开了事务后忘了COMMIT,挂了两天,期间所有DML产生的Undo都得留着。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
验证方式:
- 查
SELECT TRX_ID, TRX_STARTED, TRX_STATE FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED LIMIT 5 - 对比
SELECT COUNT(*) FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), TRX_STARTED)) > 300(查超5分钟事务) - 看
SHOW VARIABLES LIKE 'innodb_undo_tablespaces'确认是否启用独立Undo表空间
为什么回滚反而更慢?
长事务一旦触发ROLLBACK,InnoDB不是简单“删掉Undo Log”,而是要逐条执行逆操作:对每条INSERT做DELETE,对每条UPDATE用Undo里的旧值再覆写回去。这过程要重放全部Undo记录,且必须加锁、写Redo、更新Buffer Pool——本质上是一次高开销的“反向事务”。
更麻烦的是,回滚期间该事务仍持有所有行锁和GAP锁,其他事务全被堵住。线上曾有案例:一个2小时未提交的导入事务,最终回滚耗时11分钟,期间订单库完全不可写。
真正难处理的从来不是“怎么让Undo变小”,而是“怎么不让事务活这么久”。监控INNODB_TRX、限制应用层事务超时、避免在事务里调远程接口或做耗时计算,比调任何innodb_*参数都管用。










