
回滚段膨胀导致 INSERT/UPDATE 变慢怎么办
MySQL 的事务回滚不是“撤销操作”,而是靠 undo log 重放反向逻辑——这意味着长事务、大事务会持续占用 undo space,同时阻塞 purge 线程清理旧版本,最终拖慢所有写入。
常见现象:SHOW ENGINE INNODB STATUS 里看到 History list length 持续上万;performance_schema.data_locks 显示大量 RECORD 锁未释放;INSERT 延迟突然升高且 innodb_row_lock_waits 上升。
- 立即查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), TRX_STARTED)) > 60; - 别依赖
autocommit=1就万事大吉——ORM(如 Django/SQLAlchemy)常隐式开启事务,需检查transaction.atomic或session.begin()是否漏commit/rollback - 避免在事务里做 HTTP 调用、文件读写等外部耗时操作
- 大表批量更新务必分块,单事务控制在 1000 行以内,用
WHERE id BETWEEN ? AND ?+ 循环提交
innodb_rollback_on_timeout 开还是关
这个配置只影响死锁超时(Lock wait timeout exceeded),不影响普通语句超时。默认 OFF,即超时后事务仍活跃,只是报错——此时若不手动 ROLLBACK,该事务继续持有锁和 undo log,危害远大于设为 ON。
- 生产环境建议设为
ON:SET GLOBAL innodb_rollback_on_timeout = ON; - 但要注意:应用层必须能正确捕获
ERROR 1205 (40001): Deadlock found when trying to get lock和ERROR 1205 (40001): Lock wait timeout exceeded,并重试或清理资源 - 该变量不生效于
XA事务和显式START TRANSACTION WITH CONSISTENT SNAPSHOT
undo 表空间爆满后怎么紧急缩容
当 ibdata1 或独立 undo 表空间占满磁盘,INSERT 会直接报 ERROR 1114 (HY000): The table 'xxx' is full,此时不能删数据、不能停库重建——得靠在线 purge 加速。
- 先确认是否启用独立 undo 表空间:
SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'innodb_undo_tablespaces';,值 > 0 才可单独处理 - 加速清理:
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;(默认 128,越小 purge 越勤) - 临时禁用历史版本保留:
SET GLOBAL innodb_max_purge_lag = 0;(仅限紧急,会增加 MVCC 读取压力) - 真正缩容要等 purge 完成后执行
ALTER UNDO TABLESPACE <code>undo_001INACTIVE;,再用DROP UNDO TABLESPACE(MySQL 8.0.23+ 支持)
为什么 ROLLBACK 本身也变慢了
回滚变慢不是因为“撤销难”,而是 undo log 太大、磁盘随机 IO 高、或者 purge 线程被卡住,导致回滚过程要反复读取分散的 undo page。
- 监控关键指标:
innodb_undo_log_truncated(表示 purge 主动截断失败)、innodb_pages_read(回滚期间陡增说明 IO 瓶颈) - 避免在从库执行大事务回滚——从库是单线程 apply,回滚会彻底堵死复制
- MySQL 8.0.22+ 支持
SET SESSION innodb_parallel_read_threads = 4;加速回滚中的 undo scan,但仅对大范围回滚有效 - 最稳妥的解法:把大事务拆成小事务,让每个
ROLLBACK控制在毫秒级,而不是寄希望于“回滚快”
undo 是 MySQL 最容易被当成黑盒的部分——它不显式暴露,却决定着事务吞吐、锁等待、甚至主从延迟。真正要调的从来不是某个参数,而是事务粒度本身。











