回滚慢本质是undo log积压、io瓶颈或purge卡顿,需先查history list length>5000、trx_rows_modified大且trx_started久、iostat显示await>20ms且%util=100%,再针对性干预。

大事务回滚本身不会“卡死”实例,但会严重拖慢整个 InnoDB 存储引擎的 purge、undo 扫描和 IO 调度——卡死是结果,不是现象;真正要盯的是 History list length、TRX_ROWS_MODIFIED 和磁盘 await。
怎么看回滚到底卡在哪一步
别急着 KILL 或调参数。先连上 MySQL,执行三句诊断命令:
-
SHOW ENGINE INNODB STATUS\G→ 找History list length:值 > 5000 表示 undo 日志积压严重,purge 线程已跟不上 -
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED LIMIT 5;→ 看TRX_ROWS_MODIFIED和TRX_STARTED:如果某事务改了 20 万行、开了 15 分钟,基本就是它在堵住 purge 链 -
iostat -x 1(Linux)→ 关注await和%util:若await > 20ms且%util == 100%,说明回滚正在独占磁盘 IO,不是锁问题,是物理瓶颈
KILL 之后为什么更慢
MySQL 的 KILL 不会中断 undo 重放过程,只是让线程在当前 undo page 处理完再退出。这意味着:
- 一个已运行 12 分钟、还剩 85% undo 要处理的事务,
KILL后可能还要跑 18 分钟 -
KILL的目标必须是trx_mysql_thread_id(来自INNODB_TRX),不是 connection id,否则无效 - 误杀正在 purge 的后台线程(如
purge thread)反而会让 History list 持续膨胀,恶化全局性能
紧急时能做的几件实事
没有“一键加速回滚”的配置,只有针对性干预:
- 临时提高 purge 频率:
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;(默认 128),但会显著增加 CPU 开销,用完记得调回 - 确认是否启用独立 undo 表空间:
SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'innodb_undo_tablespaces';→ 值 > 0 才可安全执行ALTER UNDO TABLESPACE undo_001 INACTIVE;+DROP UNDO TABLESPACE undo_001; - 如果业务允许丢数据且事务已超 10 分钟、
TRX_ROWS_MODIFIED > 100000,优先走重建路径:停写 →innodb_force_recovery = 3启动 → 只读导出关键表 → 清空并重搭实例
日常必须掐断的三个源头
回滚卡死是果,大事务是因。以下三点不改,下次还会爆发:
- 事务里禁止 HTTP 调用、文件读写、
SLEEP()、长循环等任何非数据库操作 - 批量写入必须分批提交,单事务控制在 1000 行以内(具体阈值按单行大小和索引复杂度微调)
- 所有 DML 操作前加
SELECT ... FOR UPDATE或UPDATE ... WHERE必须命中索引,避免锁升级成表级锁
最常被忽略的一点:History list length 不是“回滚进度条”,而是 purge 落后的刻度尺。它涨得快,说明 undo 在疯长;它降得慢,说明 purge 线程或磁盘扛不住——盯着它,比盯着 ROLLBACK 命令本身有用得多。











