mysql大事务导致从库“假健康、真卡死”是因为sql thread必须等整个事务执行完才更新复制位点,期间seconds_behind_master失真、relay log积压、并行复制退化为单线程;安全拆分需主键切片、配并行复制参数、断点续跑及幂等保障。

MySQL大事务会让从库卡死几小时,不是因为“慢”,而是因为复制进度被冻结——SQL Thread必须等整个事务执行完才更新 Seconds_Behind_Master 和 Relay_Log_Pos,期间所有后续 binlog 都在排队,监控完全失真。
大事务为什么让从库“假健康、真卡死”
从库的 SQL Thread(或 MTS 下的 worker)回放事务时,不会边执行边推进位点。一个耗时 25 秒的 UPDATE t SET status=1 WHERE id BETWEEN 1 AND 500000,在这 25 秒内:Seconds_Behind_Master 可能一直显示为 0 或旧值;Relay_Log_Space 持续暴涨(常见到 5GB+);新来的 binlog 全部积压在 relay log 文件里,无法消费。
哪怕开了 slave_parallel_workers = 8,遇到这种事务也会强制退化为单线程回放——并行复制只在事务粒度间生效,不是语句粒度。
更隐蔽的是:SHOW SLAVE STATUS 里 Slave_SQL_Running: Yes 完全不可信,它只表示线程没崩溃,不代表正在有效工作。
安全拆分 UPDATE/DELETE 必须基于主键分段
用 LIMIT + ORDER BY created_at 是高危操作:字段没索引会全表扫描加锁;并发写入导致排序结果漂移;跳过间隙后无法判断是否扫完全量,极易漏数据或重复处理。
真正可落地的方案是靠主键/唯一索引切片:
- 先查边界:
SELECT MIN(id), MAX(id) FROM t WHERE status = 'pending' - 按固定步长构造区间:
WHERE id BETWEEN 100000 AND 109999 AND status = 'pending' - 每次执行后检查
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写进一张临时表(如replication_checkpoint)或外部存储(如 Redis) - 下次启动先查这个值,从
id + 1开始继续BETWEEN - 每批操作加
timeout和重试(最多 2 次),失败就记录日志并退出,不阻塞后续批次
最容易被忽略的是:拆分逻辑本身没有事务原子性保障,INSERT INTO ... SELECT 类语句若中途失败,已写入的数据不会自动回滚,必须靠外部幂等或补偿机制兜底。











