大事务导致从库延迟几小时的根本原因是MySQL单线程SQL回放卡在长事务中,即使启用MTS也会退化为单线程;安全拆分需按主键分段、加LIMIT和WHERE条件,每次仅处理几千行。

为什么大事务会让从库延迟几个小时
根本原因是 MySQL 单线程回放(sql_thread)卡在一条大事务里,比如一个 UPDATE 改了 500 万行,它必须等这条语句彻底执行完,才能处理下一条。期间所有后续 binlog 都在积压,延迟就涨上去了。
这不是网络或磁盘慢的问题,是串行回放机制本身的瓶颈。哪怕开了 MTS(多线程复制),只要事务本身够大、够长,它仍然会退化成单线程执行——因为 MySQL 要保证事务内 DML 的顺序和原子性。
- 大事务常见于:全表更新、历史数据归档、批量导入后补索引
- 典型错误现象:
Seconds_Behind_Master持续 > 3600,但Slave_SQL_Running_State显示Reading event from the relay log或Updating rows - 别指望调高
slave_parallel_workers能解决——它只对「不同库/不同分区」的事务并行有效,对单个大事务无效
怎么安全拆分一个 UPDATE 大事务
核心原则:用主键/唯一索引分段,每次只改几千行,加 LIMIT 和 WHERE 条件,确保每批都能快速提交。不能简单按时间字段切,容易漏数据或重复。
示例:原语句是 UPDATE orders SET status = 'done' WHERE created_at ,有 800 万匹配行。
- 先查出最小和最大主键:
SELECT MIN(id), MAX(id) FROM orders WHERE created_at - 用循环分批更新:
UPDATE orders SET status = 'done' WHERE id BETWEEN 100000 AND 109999 AND created_at - 每次执行后检查影响行数,若为 0 则跳出循环;否则递增区间继续
- 务必在
WHERE中保留原始业务条件(如created_at),避免跨批次误改
MTS 多线程复制到底要不要开?怎么配才不翻车
要开,但得看清版本和参数组合。MySQL 5.7 默认的 DATABASE 级并行太弱,5.7.22+ 或 8.0 推荐用 LOGICAL_CLOCK,它基于组提交(group commit)判断事务是否可并行。
关键配置项:
-
slave_parallel_type = LOGICAL_CLOCK(不是DATABASE) -
slave_parallel_workers = 4~8(别盲目设到 16+,线程调度开销反而拖慢) -
slave_preserve_commit_order = ON(必须开,否则主从一致性可能被破坏) - 确认主库已开启
binlog_group_commit_sync_delay(非必须,但能提升组提交率)
注意:LOGICAL_CLOCK 依赖主库的组提交质量。如果主库写入压力低、事务频繁小提交,从库并行度依然上不去——这时拆事务比调 MTS 更直接有效。
拆事务时最容易被忽略的三个坑
不是语法写对就能跑通,生产环境常栽在这几个点上:
- 没加
FOR UPDATE或锁机制,导致并发拆分脚本之间互相覆盖(尤其用id BETWEEN时,多个进程可能扫到同一段) - 忘记清理 relay log,大事务拆分过程持续几小时,
relay_log_space_limit被打满,复制直接中断 - 拆分脚本没做幂等设计,失败重试时重复执行同一批——建议用中间状态表记录已处理的
id范围,或用WHERE status != 'done'再兜底
真正麻烦的从来不是“怎么拆”,而是“怎么让拆的过程不影响线上读、不引发主从不一致、失败后能干净回退”。这些细节没压住,拆得越细,翻车越快。










