回滚卡住实为单线程重放undo log:InnoDB后台逆向遍历undo链恢复前镜像,不可中断、不响应KILL、无进度反馈,表现为Rolling back状态、TRX_STATE=ROLLING BACK且TRX_ROWS_MODIFIED极大、IO和锁阻塞严重。

回滚卡住不是卡,是正在单线程重放undo log
MySQL执行ROLLBACK时不会“丢弃变更”,而是InnoDB在后台单线程逆向遍历undo log链,逐条恢复前镜像。这个过程不可中断、不响应KILL、不汇报进度,但确实在运行——你看到的“卡死”,其实是它正在读GB级的undo文件、随机写数据页、清理行锁和MDL锁。
常见错误现象:SHOW PROCESSLIST里状态长期为Rolling back或Updating;INFORMATION_SCHEMA.INNODB_TRX中TRX_STATE = 'ROLLING BACK'且TRX_ROWS_MODIFIED极大(比如超50万);其他查询被阻塞,但查不到明显锁等待。
- undo log存放在独立表空间(路径由
innodb_undo_directory指定),不在buffer pool缓存范围内,增大innodb_buffer_pool_size基本无效,甚至可能挤占OS缓存、加剧swap - 回滚期间持续持有MDL锁和行锁,导致DDL和DML全部排队,但
SHOW ENGINE INNODB STATUS\G的TRANSACTIONS段里看不到显式锁冲突 - 若事务涉及大量二级索引更新,回滚还要重建索引项,CPU和IO双高,
iostat -x 1会显示%util == 100%且await > 20ms
怎么判断是真卡住还是只是慢?看三个指标
别靠TRX_STATE下结论。真正能说明问题的是这三个实时指标:
-
SHOW ENGINE INNODB STATUS\G里找History list length:超过5000说明purge滞后严重,undo积压是主因 -
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX中对比TRX_STARTED和当前时间:如果开了20分钟、TRX_ROWS_MODIFIED达80万,基本就是它拖着整个库 -
iostat -x 1盯%util和await:若两者同时飙高,说明回滚正在抢磁盘IO,不是锁也不是SQL逻辑问题
容易踩的坑:TRX_OPERATION_STATE显示fetching rows不代表在查表——它常指正在读undo page;KILL后线程状态变Killed,但TRX_STATE仍为ROLLING BACK,这是正常行为,不代表没生效。
哪些操作真有用,哪些纯属浪费时间
盲目调参或重启只会让问题更糟。实测有效的干预点只有三个层级:
- 紧急止血:
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1(默认128),加快purge节奏,但别长期开着,CPU开销明显上升 - 释放空间:确认
innodb_undo_tablespaces > 0后,对空闲undo表空间执行ALTER UNDO TABLESPACE undo_001 INACTIVE再DROP,不删数据只清空间 - 重建兜底:若事务持续超10分钟、
TRX_ROWS_MODIFIED > 100000且业务允许丢数据,停写→设innodb_force_recovery = 3重启→只读导出→重建实例。务必跳过SET FOREIGN_KEY_CHECKS=0,否则导入必卡外键
无效操作包括:innodb_buffer_pool_size调大、innodb_log_file_size改大、KILL后反复重试、重启服务期望“跳过回滚”——重启后MySQL会进入crash recovery,单线程重放未完成undo,耗时往往更长,还可能因page校验失败卡死在Starting crash recovery。
日常必须做好的两件事,否则下次还会卡
回滚慢从来不是突发故障,是长事务+IO弱+purge配置僵化的结果。最容易被忽略的是:
- MySQL 8.0默认
innodb_max_purge_lag = 0,等于关掉了undo堆积保护——它不自动触发purge,只等你用监控+应用治理兜底 - 上线前必须启用独立undo表空间:
innodb_undo_tablespaces >= 2,否则下次回滚慢,你连安全缩容undo的空间都没有
复杂点在于:undo log的生成发生在事务提交前,而回滚性能取决于它——你想优化回滚,得在DML执行阶段就控制日志量。分批操作、加事务超时、避免全表删除/更新,这些才是源头解法。等看到Rolling back再动手,已经晚了。


















