大事务会阻塞从库sql_thread导致主从延迟,因其不可中断且必须串行执行;应kill非关键大事务、用pt-archiver分批提交、拆分insert/delete语句并加limit和order by,确保binlog顺序可维护。

大事务卡住从库同步怎么办
MySQL 大事务会阻塞从库的 SQL_THREAD,导致主从延迟飙升,甚至触发 Seconds_Behind_Master 持续为 0 但实际已落后——这是因为从库在重放一个超长事务时,无法并发处理后续事件。
根本原因不是“慢”,而是“不可中断”:一个事务内所有 DML 都绑定在同一个 GTID 下,从库必须串行执行完全部语句才能推进位点。
- 查出正在运行的大事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60; - 优先 kill 掉非业务关键的大事务(如历史数据归档),避免用
KILL强杀正在写 binlog 的事务,可能引发主从不一致 - 对必须执行的长事务(如千万级表 cleanup),改用
pt-archiver分批提交,每批控制在 1k–5k 行,显式加COMMIT
INSERT ... SELECT 卡死整个实例?
这个语句默认开启一个隐式事务,如果源表有 5000 万行,目标表又没索引,它会一次性锁住所有扫描行、生成巨量 undo 日志,并把 binlog 写成单个 event——这会让备库回放时 CPU 和磁盘 I/O 同时打满。
更糟的是,在 READ-COMMITTED 隔离级别下,它仍会持有全表范围的 gap lock,阻塞其他写入。
- 拆成带
LIMIT的循环插入:INSERT INTO t2 SELECT * FROM t1 WHERE id BETWEEN ? AND ?;,每次取 1w 行,用应用层或存储过程控制 offset - 确保目标表有主键或唯一索引,否则 MySQL 会退化为全表扫描 + 文件排序
- 临时关闭 binlog(仅限从库修复场景):
SET SESSION sql_log_bin = 0;,但主库严禁这么做
DELETE 无 LIMIT 为什么比 UPDATE 更危险
DELETE FROM user WHERE status = 0 这类语句在 InnoDB 中会先构建完整的二级索引变更列表,再逐个清理聚簇索引记录。若匹配 200 万行,它会产生等量的 undo log、redo log 和 binlog,且全程持有行锁和自增锁(即使没用到自增列)。
而同样条件的 UPDATE 至少还能利用覆盖索引减少回表,DELETE 几乎没法优化。
- 强制加
LIMIT并循环:DELETE FROM user WHERE status = 0 ORDER BY id LIMIT 1000;,注意必须带ORDER BY防止重复删 - 用主键范围分片删除:
DELETE FROM user WHERE status = 0 AND id BETWEEN 1000000 AND 1999999;,提前预估 ID 分布 - 避免在高峰期执行;如需定时清理,把任务切到低峰期并监控
Innodb_row_lock_waits
事务拆分后 binlog 顺序还能保证吗
能,但只在单线程写入前提下。MySQL 5.7+ 的 binlog_order_commits=ON(默认)会强制按 commit 顺序写 binlog,所以即使你把一个逻辑事务拆成 100 个小事务,它们的 binlog event 时间戳仍是严格递增的。
真正风险来自并发写入:多个连接同时拆分执行,彼此 commit 时间交错,binlog 里就变成混合顺序。这时候从库即使开了 slave_parallel_workers,也可能因 GTID 冲突退回到单线程回放。
- 用同一连接串行执行所有拆分批次,避免连接池自动分配不同连接
- 必要时加
SELECT GET_LOCK('cleanup_task', 10)做分布式互斥,防止多实例同时跑同个清理脚本 - 不要依赖 binlog 顺序做业务幂等判断;应该用业务字段(如
updated_at)+ 唯一约束兜底
事务不是越大越稳,而是越小越可控。很多人卡在“怕拆分会丢数据”,其实真正容易丢的是没监控的大事务失败后的补偿逻辑——那才是最该花时间写的部分。











