主从延迟突增通常因单个大UPDATE语句修改数百万行所致;需通过SHOW PROCESSLIST或performance_schema定位卡住的SQL,结合慢日志与EXPLAIN分析扫描行数,再基于有索引的单调字段分批更新。

主从延迟突增时,先看是不是单个 UPDATE 太大
MySQL 主从延迟飙升,90% 的情况不是网络或硬件问题,而是某个 UPDATE 语句一次性改了几百万行。从库 SQL 线程是单线程回放(除非开了 slave_parallel_workers 且表有合理分区),遇到大事务只能硬扛,延迟秒变小时级。
实操建议:
- 用
SHOW PROCESSLIST或performance_schema.replication_applier_status_by_worker确认从库是否卡在某条UPDATE上 - 查主库慢日志,过滤出执行时间长、扫描行数多的
UPDATE,重点关注没走索引或WHERE条件太宽泛的语句 - 临时加
EXPLAIN UPDATE ...(5.7+ 支持)看实际扫描行数,别只信EXPLAIN SELECT
把大更新拆成小批量,但别盲目设 LIMIT 1000
拆分不是简单套个 LIMIT 就完事。MySQL 的 UPDATE ... LIMIT 在有 ORDER BY 缺失时行为不可控,可能漏更新或重复更新;更糟的是,如果 WHERE 条件字段没索引,每次 LIMIT 都要全表扫一遍,越往后越慢。
实操建议:
- 必须基于有索引的单调字段(如
id、create_time)做游标分页,例如:UPDATE t SET status=1 WHERE id > 10000 AND id - 每批控制在 1k–5k 行,太大仍会锁表久、主从同步压力集中;太小则网络和事务开销占比高
- 拆分逻辑不要写在应用层循环里直接发 N 条语句,用存储过程或脚本控制节奏,每批后加
SLEEP(0.1)给从库喘息时间
避免事务过大导致 binlog 写入和回滚段膨胀
一个更新 50 万行的事务,binlog 文件可能暴涨几百 MB,主库磁盘 IO、从库解析压力都剧增;同时 innodb_undo_tablespaces 可能撑爆,甚至触发 ERROR 1205 (HY000): Deadlock found。
实操建议:
- 显式加
BEGIN/COMMIT,每批更新独立事务,禁止包在一个大事务里 - 确认
innodb_log_file_size足够(建议 ≥ 1GB),否则小事务频繁提交也会因刷 redo 太勤拖慢性能 - 更新前关掉
autocommit是为了控制事务边界,但别忘了手动COMMIT—— 曾见有人脚本里漏写,结果锁一直挂着
从库延迟敏感场景下,慎用 ROW 格式 + 大更新
binlog_format=ROW 虽然精确,但大更新会把每一行变更都记进 binlog。比如更新 10 万行,binlog 就写 10 万条 Update_rows_event,从库解析+应用成本远高于 STATEMENT 模式下的单条语句——尤其当更新字段少、条件简单时。
实操建议:
- 非必要不临时切
STATEMENT,但可评估:如果更新逻辑确定安全(无NOW()、UUID()、用户变量等非确定函数),且表有主键,STATEMENT回放更快 - 生产环境切格式前,先在从库停复制,用
mysqlbinlog --base64-output=DECODE-ROWS -v对比两种格式下 binlog 体积差异 - 注意
STATEMENT下UPDATE ... LIMIT可能主从不一致,必须搭配确定性WHERE条件(如主键范围)
真正难的不是拆分动作本身,而是判断哪批数据该优先更新、哪些字段更新可以合并、以及如何让运维能一眼看出当前进度卡在哪条游标上——这些细节没埋进脚本里,光靠人工盯日志,延迟永远在你松懈时爆发。










