大事务是主从延迟最顽固的根源,必须通过拆分(如分批DELETE+显式COMMIT)和监控(binlog增长速率、长事务行数告警)双管齐下;需避免全表扫描删除、禁用autocommit、评估binlog_row_image配置,并识别隐形大事务。

大事务是主从延迟最顽固的根源之一,光靠调参解决不了——必须拆分+监控双管齐下。
为什么大事务会让从库“卡死”?
MySQL 8.0 的复制是单线程 SQL 回放(除非启用并行),一个 DELETE FROM orders WHERE created_at 删除 500 万行的事务,在主库可能耗时 8 秒提交,但 binlog event 会打包成一条巨量日志;从库 SQL 线程必须完整重放这 500 万行变更,期间无法处理后续任何事务,<code>Seconds_Behind_Master 直接跳涨几十秒甚至几分钟。
更糟的是:即使你开了 slave_parallel_workers=16,WRITESET 并行也对这种单事务内部高冲突操作无效——它只在“多个事务之间”判断可否并行,不是把一个大事务切开跑。
- 大事务不会触发并行调度,只会被当作一个原子单元排队等待
- relay log 写入和 SQL 回放都集中在单个线程,磁盘 I/O 和 CPU 都被锁死
- 主库
binlog_group_commit_sync_delay再小,也无法缓解单事务本身带来的阻塞
怎么安全拆分大事务?关键三步
不能简单套用 LIMIT 就完事,必须保证语义正确、不漏不重、不影响业务。
- 用唯一递增字段(如
id)分片,避免WHERE created_at 在高并发写入下漏数据 - 每次执行后记录最大已处理
id,下次从WHERE id > ?继续,而不是依赖时间字段 - 控制每批
LIMIT数量:建议 1k–5k 行(视单行大小和索引效率调整),太大仍会锁表/慢日志报警 - 显式加
COMMIT,禁用自动提交:SET autocommit = 0,否则每批都会触发一次 binlog 刷盘开销
示例(安全分页删除):
START TRANSACTION; DELETE FROM orders WHERE id BETWEEN 1000001 AND 1001000; COMMIT;
别用 ORDER BY id LIMIT 1000 —— 没有 WHERE 条件时,MySQL 可能全表扫描再排序,性能雪崩。
如何监控 binlog 生成速度是否异常?
光看 Seconds_Behind_Master 是马后炮。真正要盯的是主库 binlog 的“生产节奏”:如果某段时间内 binlog 文件增长突兀(比如 1 分钟涨了 200MB),基本就是大事务正在发生。
- 查当前 binlog 大小:
SHOW MASTER LOGS;,对比File_size变化趋势 - 实时观察写入速率:
tail -f /var/lib/mysql/mysql-bin.000007 | hexdump -C | head不实用,改用inotifywait+stat脚本轮询文件大小(生产环境慎用) - 更稳的方式:开启
performance_schema的events_statements_history_long,过滤出执行时间 > 5s 且影响行数 > 10000 的语句 - 阿里云 RDS 用户可直接看控制台「慢日志分析」+「Binlog 增长速率」曲线,阈值建议设为 50MB/min
注意:binlog_row_image=FULL 会显著增大 binlog 体积(尤其 UPDATE/DELETE),若业务允许,可评估是否切回 MINIMAL,但需确认所有被更新字段都有索引,否则 WRITESET 并行会退化。
拆完事务后,还得防“隐形大事务”
有些事务表面看不出大,实则暗藏风险:
-
INSERT ... SELECT从千万级表拉数据:实际是单事务读+写,binlog 日志量 = 所有插入行的 row image 总和 - 存储过程里循环调用
INSERT但没手动COMMIT:整个过程算一个事务 - 应用层用 ORM 的批量 save(),底层未分批,生成单条超长 binlog
这类场景必须在应用代码里强制分批,并在 MySQL 侧用 SELECT @@innodb_trx.trx_rows_modified 或 performance_schema.events_transactions_current 查看运行中事务的修改行数,超过阈值(如 5000)就告警干预。
真正难的不是拆,而是识别——很多大事务根本不在慢日志里(因为没超 long_query_time),却在默默拖垮复制链路。











