大事务让从库卡住不动是因为单个事务内修改行数多、行锁时间长、binlog_format=row三者叠加,压垮单线程sql回放;必须用游标式分批(如where id > @last_id)、每批显式commit、避免offset,并监控gtid差值和pt-heartbeat验证真实延迟。

大事务为什么让从库卡住不动
不是数据量大就一定慢,而是「单个事务内修改行数多 + 行锁时间长 + binlog_format=ROW」三者叠加,直接压垮从库SQL线程。主库一条UPDATE改50万行,在ROW格式下会生成50万条Update_rows_log_event,从库只能串行回放——它不是不想快,是MySQL默认SQL Thread就是单线程的。
常见错误现象:Seconds_Behind_Master突然跳到几千秒、Exec_Master_Log_Pos几乎不动、Slave_SQL_Running_State卡在executing event,同时主库SHOW PROCESSLIST早结束了,但从库还在跑一条Update_rows_log_event。
拆分大事务的实操要点
别信LIMIT 1000 OFFSET N,OFFSET越大越慢,且并发时容易漏行或重复。必须用游标式推进:
- 先确保WHERE条件字段有高效索引(比如
status字段上建了索引) - 用自增主键或时间戳做游标:例如
WHERE id > @last_id AND status = 'pending' ORDER BY id LIMIT 1000 - 每次取完立刻记录最大
id:SELECT @last_id := MAX(id) FROM orders WHERE id > @last_id AND status = 'processed' LIMIT 1 - 每批执行后显式
COMMIT,确保每个分片是独立事务
示例伪代码里@last_id变量必须初始化,否则第一次查询可能全表扫。
从库上哪些操作会放大延迟
慢查询和DDL不是“旁观者”,它们会直接抢占SQL线程的CPU和IO资源:
-
SHOW PROCESSLIST里看到长时间运行的SELECT,尤其是带JOIN、ORDER BY、GROUP BY的报表SQL - 主库执行
ALTER TABLE后,从库SQL线程可能卡在Waiting for table metadata lock - 从库磁盘I/O等待高(
iostat -x 1中%util持续>90%),说明relay log写入或回放已受阻
这类问题不能只调slave_parallel_workers,得先停掉非必要查询,再查performance_schema.replication_applier_status_by_worker确认是否真在并行回放。
监控和验证延迟是否真实
Seconds_Behind_Master经常“说谎”:主从时钟不同步时可能为负;IO线程还没拉完日志时它显示0;大事务执行中它只记BEGIN时间戳,不反映实际进度。
更可靠的验证方式:
- 用
pt-heartbeat写心跳表,对比主从时间差(秒级精度) - 看
Read_Master_Log_Pos和Exec_Master_Log_Pos差值:差得大说明IO线程拖后腿;差得小但Seconds_Behind_Master高,基本确定是SQL线程卡住了 - 检查GTID差值:
SELECT GTID_SUBTRACT(@@global.gtid_executed, @@global.gtid_slave_pos),结果非空说明确实落后
真正难的不是拆事务,而是把业务侧的定时任务、报表脚本、MQ消费逻辑全部拉出来,逐个确认它们是否在从库上跑了不该跑的查询——这才是延迟反复复发的根因。











