大事务导致主备切换失败,因备库需等其完全回放才能追平gtid,而切换脚本依赖同步完成(如seconds_behind_master=0)才继续,从而卡住;安全拆分须用带主键/索引的where+limit分批(每批1k~5k行),避免全表扫描。

大事务为什么会让主备切换失败
主备切换时,如果新主库上还卡着一个未提交的大事务(比如 UPDATE 十万行、DELETE 百万历史日志),它会直接拖垮整个切换流程。因为:备库必须等这个事务完整回放完,才能追平主库的 Executed_Gtid_Set;而切换脚本通常依赖 Seconds_Behind_Master = 0 或 GTID_SUBSET() 验证通过才继续。结果就是卡在“等待同步完成”这一步,动弹不得。
怎么安全拆分大事务(不锁表、不堵复制)
核心原则是:用 WHERE + LIMIT 分批,每次控制在 1k~5k 行,且必须带主键或二级索引条件。否则会触发全表扫描,反而加重备库压力。
- 错误写法:
DELETE FROM logs WHERE create_time (没 LIMIT,没索引,单次执行几小时) - 正确写法:
DELETE FROM logs WHERE id > 1000000 AND id (用主键分段,加索引字段过滤) - 自动化脚本要点:每次执行后查
SELECT ROW_COUNT(),若为 0 则退出;循环间隔加SLEEP(0.1),避免压垮 relay log 回放线程
切换前必须验证的两个 GTID 状态
哪怕你已经拆完大事务,也得确认新主库真正“干净”了——否则切过去立刻丢数据。关键不是看 Seconds_Behind_Master,而是比对 GTID 集合:
- 先在旧从库上执行:
SELECT @@global.gtid_executed INTO @old_slave_gtid - 再在新主库上执行:
SELECT @@global.gtid_executed INTO @new_master_gtid - 最后验证:
SELECT GTID_SUBSET(@old_slave_gtid, @new_master_gtid),返回1才算安全
注意:GTID_SUBSET() 是单向包含判断,不能反过来用。很多运维误写成 GTID_SUBSET(@new_master_gtid, @old_slave_gtid),结果永远返回 0,白白卡住切换。
拆事务后仍延迟?检查并行复制是否真生效
拆分只是治标,备库回放慢才是病根。MySQL 5.7+ 默认的 slave_parallel_type = DATABASE 在单库场景下等于没开并行——所有 SQL 都塞进同一个 worker 线程里排队。
- 必须改成:
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK'(改前先STOP SLAVE) - 同时调高并发数:
SET GLOBAL slave_parallel_workers = 4(根据 CPU 核数设,别超 8) - 验证是否生效:
SHOW PROCESSLIST里应看到多个Slave_worker线程在运行,且SHOW SLAVE STATUS\G中Slave_SQL_Running_State显示 “Reading event from the relay log” 而非 “Waiting for an event from Coordinator”
最常被忽略的一点:slave_parallel_workers 设了但没重启 SQL 线程,或者忘了 START SLAVE,配置就一直躺在那里睡大觉。











