大批量update卡住从库sql线程因单线程回放且逐行重放耗时倍增;必须基于有索引的单调字段(如id)游标分页拆分,每批1000–5000行并加sleep,禁用limit盲目截断。

为什么大批量 UPDATE 会卡住从库 SQL 线程
因为 MySQL 从库默认用单线程回放 binlog(slave_parallel_workers=0),而一个影响百万行的 UPDATE 在主库可能几秒跑完,但从库要逐行重放、加锁、刷脏页、写 undo,实际耗时可能是主库的 5–10 倍。更关键的是:这个大事务会阻塞后续所有事务的执行,直到它彻底完成——延迟不是“慢”,而是“排队等它”。
拆分 UPDATE 不能只加 LIMIT
直接写 UPDATE t SET status=1 WHERE create_time 是危险的:
- 没
ORDER BY时,LIMIT行为不可控,可能漏更新或重复更新 - WHERE 字段无索引?每次
LIMIT都全表扫描,越往后越慢,甚至比原语句还糟 - 没显式事务控制,autocommit 开启时每条都独立提交,redo 刷盘频繁拖慢性能
必须用游标分页 + 有索引字段拆分
核心是让每批更新都基于一个单调、有索引的字段(如 id 或 create_time)做范围推进,确保不跳、不重、不扫全表:
- 先确认该字段有索引:
SHOW INDEX FROM t WHERE Key_name = 'idx_id'; - 示例拆分逻辑:
UPDATE t SET status=1 WHERE id > 10000 AND id - 每批控制在 1000–5000 行,太大仍锁表久,太小事务开销占比高
- 每批后加
SLEEP(0.05)或SELECT SLEEP(0.1),给从库喘息时间
事务边界和 binlog_format 的隐性影响
拆分不是只改 SQL,还要管事务和日志格式:
- 显式用
BEGIN/COMMIT包裹每批,禁止包在一个超长事务里 - 检查
binlog_format:SELECT @@binlog_format;,若为STATEMENT,大更新在从库可能因统计信息偏差走错执行计划;优先切到ROW,但要注意:更新 10 万行 → binlog 写 10 万条事件,从库解析压力翻倍 - 如果只是字段少、条件简单,
STATEMENT反而更轻量,别盲目切ROW
autocommit 导致每条都刷 redo;或者 slave_parallel_workers 设了 8,却没配 slave_parallel_type = LOGICAL_CLOCK,结果并行根本没生效。这些点不查 SHOW SLAVE STATUS\G 和 SHOW PROCESSLIST,光看 Seconds_Behind_Master 是发现不了的。











