大事务恢复慢、锁等待高,根本原因是回滚段膨胀和锁持有过久;应拆分为小批量提交(如每1000~5000行),显式commit,确保where命中索引,以同步压低undo log增长、缩短锁时间、缓解主从延迟。

大事务恢复慢、锁等待高,根本不是日志没刷完,而是回滚段膨胀+锁持有太久。直接拆成小批量提交,能同时压低 undo log 增长、缩短锁时间、缓解主从延迟,比调 innodb_log_file_size 或等崩溃恢复更立竿见影。
为什么大事务恢复特别卡锁等待
MySQL 崩溃后重启,InnoDB 要重放 redo log 并回滚未提交事务。一个百万行的 DELETE 大事务,undo log 可能占几百 MB,回滚过程会持续加锁、阻塞新事务——尤其是它还在扫描旧版本链时,其他查询可能被堵在 SELECT ... FOR UPDATE 上动弹不得。
- 回滚不是“瞬间撤销”,而是逐行逆向执行,耗 CPU 且持锁
- 大事务写入大量 undo page,触发 purge 线程压力,进一步拖慢并发 DML
- 主库恢复慢 → 从库 relay log 积压 →
Seconds_Behind_Master暴涨 → 应用读从库超时
怎么拆:用 LIMIT + 显式 COMMIT 控制每批大小
别依赖 ORM 自动事务或框架默认的“整个请求包进一个事务”。必须手动切片、显式提交。
- 单次处理行数控制在
1000~5000,具体看单行数据大小和索引效率;超过 5000 行易触发max_allowed_packet或锁等待陡增 - 每次
DELETE或UPDATE后立刻COMMIT,不要等循环结束 - 加
SLEEP(0.05)(50ms)可缓解从库压力,但别在主库上滥用,避免人为拉长总耗时 - WHERE 条件必须命中索引,否则
LIMIT无效:MySQL 仍要全表扫描才能找到前 N 行
WHILE ROW_COUNT() > 0 DO DELETE FROM orders WHERE status = 'cancelled' AND created_at <h3>容易踩的坑:拆了但还是锁满屏</h3> <p>拆批 ≠ 自动解锁。很多团队拆了却仍卡住,问题出在细节:</p>
-
LIMIT不带ORDER BY:InnoDB 扫描顺序不确定,可能反复扫同一块页,导致锁范围抖动甚至死锁 - 没确认索引是否生效:用
EXPLAIN看type是range还是ALL;key列是否为预期索引名 - 事务外还裹着一层隐式事务:比如 Spring 的
@Transactional注解没配propagation,导致每个DELETE实际还在大事务里 - 用
IN (id1,id2,...)替代LIMIT时,列表长度超max_allowed_packet,报错Packets larger than max_allowed_packet are not allowed
补充手段:配合隔离级别与监控快速见效
拆批是主干,但单靠它不够。上线前务必同步做两件事:
- 临时将该任务会话设为
READ COMMITTED:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED—— 避免REPEATABLE READ下的间隙锁放大锁范围 - 上线中盯紧
information_schema.INNODB_TRX,查trx_started和trx_state,一旦发现某事务运行超 60 秒,立刻KILL掉,防止它变成“钉子户”拖垮整库
真正难的不是写循环脚本,而是把业务逻辑里那些“顺手加的远程调用”“临时拼的 JSON 字段”“没想清楚就塞进事务的日志记录”全踢出去——锁等待不会骗人,它只忠实地反映你事务里混进了多少不该有的东西。











