应查询information_schema.INNODB_TRX表中TRX_STATE='RUNNING'且TRX_STARTED远早于当前时间、TRX_ROWS_MODIFIED极大的长事务,并关联PROCESSLIST定位SQL;因其阻塞purge线程导致Undo Log无法清理。

查哪些事务在疯狂写Undo Log
Undo Log暴涨通常不是突然发生的,而是被长时间未提交的事务持续占用空间导致。最直接的办法是看当前活跃事务里谁卡住了:information_schema.INNODB_TRX 是核心入口,重点关注 TRX_STARTED、TRX_STATE 和 TRX_ROWS_MODIFIED 字段。
-
TRX_STATE = 'RUNNING'且TRX_STARTED时间远早于当前时间(比如几小时甚至几天),基本就是嫌疑对象 -
TRX_ROWS_MODIFIED数值极大(如 >10万)但事务没提交,说明它一边改数据一边把旧版本全堆进Undo Log - 搭配
information_schema.PROCESSLIST查ID对应的INFO字段,能定位到具体SQL,比如UPDATE orders SET status = 'done' WHERE user_id IN (...)这类批量更新没加 LIMIT 又没提交
为什么长事务会让Undo Log删不掉
MySQL 的 Undo Log 不是“用完即焚”,它得留着供其他并发事务做 MVCC 一致性读。只要有一个老事务还开着,哪怕它啥都不干,所有它启动时刻之后产生的 Undo Log 都不能被 purge 线程清理。
- InnoDB 的
purge_sys只能清理“早于最老活跃事务快照”的Undo页,这个最老事务就是瓶颈点 - 如果应用层用了
SET autocommit = 0却忘了COMMIT或ROLLBACK,或者 ORM 框架连接池配置不当导致事务泄漏,就会卡住整个 purge 流程 - 注意:只读事务(
TRX_ISOLATION_LEVEL = 'REPEATABLE READ'且没执行写操作)也会持有快照,但不会新增 Undo;真正危险的是“写+不提交”
监控和自动发现长事务的实用命令
别等磁盘告警才去查,得把检测变成日常巡检动作。以下命令可直接丢进脚本或Prometheus exporter里:
SELECT trx_id, trx_started, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec, trx_state, trx_rows_modified, SUBSTRING_INDEX(trx_mysql_thread_id, ':', 1) AS thread_id FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 600;
- 阈值设成 600 秒(10分钟)比较合理,太短会误报,太长可能已造成空间压力
- 结果里的
thread_id可直接关联information_schema.PROCESSLIST.ID查原始SQL和客户端IP - 生产环境建议加
ORDER BY duration_sec DESC LIMIT 5,优先盯住Top 5“钉子户”
回滚失败或卡死时怎么安全处理
发现长事务后第一反应不是立刻 KILL,尤其当 TRX_ROWS_MODIFIED 很大时,强行中断会导致回滚过程本身吃满IO、拖慢整个实例。
- 先确认它是否还在活动:查
PROCESSLIST.COMMAND是否为'Sleep'或'Query',如果是'Killed'就别动,等它自己结束 - 如果必须终止,优先用
KILL QUERY {thread_id}中断当前语句(不杀连接),比KILL {thread_id}更温和 - 回滚中出现
Lock wait timeout exceeded或InnoDB: Trying to access page number ... in space 0错误,说明Undo页已损坏或系统资源耗尽,此时只能重启实例——但务必先备份ibdata1和 binlog 位置
真正麻烦的不是查出长事务,而是它背后那个没加超时控制的业务逻辑。比如一个导出接口手动开启事务却依赖前端点击“完成”才提交,用户关了页面,事务就永远挂着。










