大事务会让从库“卡死”而非“变慢”,因为MySQL从库SQL线程默认单线程回放,将整个事务视为原子单元执行,期间阻塞后续所有binlog回放,且并行复制(WRITESET/LOGICAL_CLOCK)无法拆分单个事务。

大事务为什么会让从库“卡死”,而不是“变慢”
因为 MySQL 从库的 SQL 线程默认是单线程回放——它不拆解事务,只把整个 UPDATE 或 DELETE 当作一个原子单元排队执行。哪怕主库只花了 3 秒提交,从库也要完整重放那几百万行变更,期间 Seconds_Behind_Master 会线性跳涨,后续所有 binlog 全部堵在 relay log 里不动。
更关键的是:开了 slave_parallel_workers 也救不了它。WRITESET 并行只在“多个事务之间”调度,不会把一个大事务切开跑;slave_parallel_type = LOGICAL_CLOCK 同样无效,因为它依赖事务 commit 时间戳,而大事务只有一个时间戳。
安全拆分 UPDATE/DELETE 的三步铁律
用 LIMIT + ORDER BY created_at 是最常见也最危险的写法。并发写入、索引缺失、主键空洞都会导致漏改或重复改。
- 必须基于自增主键或唯一索引字段分段,例如
WHERE id BETWEEN ? AND ? - 每次执行后查
ROW_COUNT():返回 0 就立即退出,避免空扫 - 每批后显式
COMMIT,且禁用AUTOCOMMIT;否则每批都触发一次 binlog 刷盘 - 不要用
OFFSET分页,改用游标式推进:WHERE id > last_id ORDER BY id LIMIT 5000
从库并行复制不是“开了就生效”
光设 slave_parallel_workers = 16 没用。三个配置缺一不可:
-
slave_parallel_type必须为LOGICAL_CLOCK(5.7+)或WRITESET(8.0+),设成DATABASE基本无效 - 主库
binlog_row_image必须为FULL,否则从库无法生成逻辑时钟依赖 -
slave_preserve_commit_order = ON,否则多 worker 可能破坏事务顺序,引发主从不一致
验证是否真生效:执行 SHOW PROCESSLIST 应看到多个 Worker 线程,且 SHOW SLAVE STATUS\G 中 Slave_SQL_Running_State 显示类似 worker 2 is waiting for an event from coordinator。
拆分脚本必须带断点续跑和超时控制
生产环境没有“一次跑完”的机会。网络抖动、锁等待、磁盘满都可能中断。不记录断点,下次就得全表重扫。
- 每次成功处理完一个
BETWEEN区间后,把最大id写入临时表(如replica_split_checkpoint) - 下次启动先查该值,从
WHERE id > ?开始继续 - 单次
UPDATE加超时(例如max_execution_time = 5000),超时直接报错退出 - 内置重试逻辑(最多 3 次),避免某一批卡住导致整个流程挂死
最容易被忽略的其实是最后一点:没人会盯着脚本跑完,但没人会容忍它半夜卡住、拖垮从库一整晚。


















