大事务导致从库卡死几小时,是因为SQL Thread必须等整个事务执行完才更新Seconds_Behind_Master和Relay_Log_Pos,期间所有后续binlog排队等待,延迟实际冻结而非缓慢追赶;即使启用MTS也会退化为单线程回放。

直接拆事务,别碰 LIMIT + ORDER BY,否则漏数据或重复更新。
为什么大事务会让从库卡死几小时
MySQL 从库的 SQL Thread(或 MTS 下的 worker)必须等整个事务执行完,才更新 Seconds_Behind_Master 和 Relay_Log_Pos。一个改 500 万行的 UPDATE,在从库上跑 28 秒,这 28 秒里所有后续 binlog 都排队等着——延迟不是“慢慢追”,而是“完全冻结”。监控显示 Seconds_Behind_Master 还是 0,其实已经积压了几千条日志。
- 哪怕开了
slave_parallel_workers,遇到大事务也会退化为单线程回放 -
SHOW SLAVE STATUS里Slave_SQL_Running: Yes不代表健康,可能正卡在一条语句里不动 - 延迟飙升时,
Relay_Log_Space会暴涨(比如涨到 6GB+),这是 SQL 线程处理不过来的铁证
安全拆分 UPDATE/DELETE 必须基于主键分段
用 LIMIT 加 ORDER BY created_at 是典型错误:字段没索引、并发写入导致排序结果漂移、跳过间隙后无法判断是否结束。真正可落地的方案是靠主键/唯一索引切片:
- 先查边界:
SELECT MIN(id), MAX(id) FROM t WHERE status = 'pending' AND created_at - 按固定步长构造区间:
WHERE id BETWEEN 100000 AND 109999 AND status = 'pending' AND created_at - 每次执行后检查
ROW_COUNT():为 0 就立即退出循环,避免空跑 - 每批独立
COMMIT,不能包在一个大事务里
拆完还得配对并行复制参数
只拆事务不调参数,小事务照样串行回放。关键配置必须一起改:
-
slave_parallel_type = LOGICAL_CLOCK(不是DATABASE)——MySQL 5.7.22+ / 8.0 强制要求 -
slave_parallel_workers = 4~8:设太高反而增加调度开销,别盲目堆到 16+ -
slave_preserve_commit_order = ON:必须开启,否则主从一致性可能被破坏 - 主库确认已启用组提交:
binlog_group_commit_sync_delay建议设为100000~200000微秒
生产环境必须支持断点续跑
没人能保证一次跑完。网络抖动、从库临时负载高、某批语句锁等待超时,都可能中断。脚本里不加断点逻辑,等于埋雷:
- 每次成功执行后,把最后处理的
id写进一张临时表或外部存储(如 Redis) - 下次启动先查这个值,从
id + 1开始继续BETWEEN - 每批操作加
timeout和重试(最多 2 次),失败就记录日志并退出,不阻塞后续批次
最容易被忽略的是:拆分逻辑本身没有事务原子性,UPDATE 批次失败时,不能靠回滚恢复,只能靠断点位置重试——所以断点存储必须可靠、写入必须在 COMMIT 后立即完成。


















