大事务是主从延迟的直接断点,必须拆分;limit+order by易漏/重数据,安全拆分需基于主键分段、三步操作并加超时重试。

大事务是主从延迟最直接的“断点”,不是慢,而是卡死——一个未提交的 UPDATE 改 200 万行,从库 SQL 线程就停在那儿不动,后续所有 binlog 全排队,Seconds_Behind_Master 会按秒线性上涨,而不是“等它跑完再算”。拆事务不是可选项,是必须落地的动作。
为什么 LIMIT + ORDER BY 拆 UPDATE 容易漏数据或重复?
表面看 UPDATE ... LIMIT 5000 很直观,但实际在并发写入、无索引排序、非连续主键场景下极不可靠:
-
ORDER BY created_at若该字段无索引,每次执行的“前 5000 行”物理顺序可能不同,导致部分行被跳过或重复处理 - 不带原始业务条件(如
status = 'pending'),第二批可能误改第一批已更新过的行 -
LIMIT不保证主键连续性,无法判断是否真正处理完毕;一旦中途失败,没有断点续跑依据
安全拆分必须基于主键/唯一索引分段
核心是让每批范围明确、可验证、无重叠。操作逻辑固定为三步:查边界 → 分区间 → 验结果。
- 先执行
SELECT MIN(id), MAX(id) FROM t WHERE status = 'pending' AND created_at ,拿到完整处理范围 - 用固定步长(如 10000)构造
WHERE id BETWEEN 100000 AND 109999 AND status = 'pending' AND created_at - 每次执行后检查
ROW_COUNT():若返回 0,说明该批次无匹配行,立即退出循环;否则继续下一区间
拆分后从库并行复制仍没提速?检查这三个配置项
光拆事务不够,小事务若还在单线程里串行跑,延迟只是从“小时级”变成“分钟级”。关键配置缺一不可:
-
slave_parallel_type必须设为LOGICAL_CLOCK(MySQL 5.7+)或COMMIT_ORDER(8.0+);设成DATABASE基本无效 -
binlog_row_image在主库必须为FULL,否则从库无法生成逻辑时钟依赖信息 -
slave_preserve_commit_order必须为ON,否则多 worker 并发执行可能破坏事务提交顺序,引发主从不一致
最容易被忽略的细节:拆分脚本必须支持中断与重试
生产环境不会给你一次跑完的机会。网络抖动、锁等待、临时磁盘满都可能中断执行。不记录断点,下次就得从头扫全表。
真实可行的做法是:每次成功处理完一个 BETWEEN 区间后,把最大 id 写入一张临时表(如 replica_split_checkpoint);下次启动先查这个值,从 id > ? 开始下一批。同时脚本自身要加超时(比如单次 UPDATE 超过 5 秒就报错退出)和重试逻辑(最多重试 3 次)。否则某一批卡住,整个流程挂死,反而加剧延迟。











