根本解法是拆事务;大事务在commit时需一次性flush+fsync大量binlog,因binlog_cache_size默认仅32kb易溢出至磁盘临时文件,且独占group commit队列阻塞其他事务,导致大量线程卡在writing to binlog状态。

根本解法是拆事务,不是调参;不拆事务只改binlog_cache_size或sync_binlog,延迟照卡不误。
为什么大事务会让 binlog 写入“卡住”?
MySQL 的 binlog 不是边执行边写,而是在事务 COMMIT 时一次性 flush + fsync。一个改 50 万行的 UPDATE,生成的 binlog event 可能超 100MB,但默认 binlog_cache_size = 32768(32KB),早溢出到磁盘临时文件;同时它独占 binlog group commit 队列,其他事务全得排队等它写完——SHOW PROCESSLIST 里大量线程卡在 Writing to binlog 就是典型表现。
更隐蔽的是:即使你把事务拆小了,单条语句(比如 INSERT INTO t SELECT * FROM huge_table)生成的 binlog event 若超过 max_allowed_packet(默认 4MB),仍会直接报错:Got a packet bigger than 'max_allowed_packet' bytes。
如何安全拆分 UPDATE/DELETE 大事务?
核心原则:锚定主键/唯一索引滚动推进,杜绝 LIMIT + OFFSET 或无索引时间字段。
- 用
WHERE id BETWEEN ? AND ?分段,每次取固定步长(如 5000–10000 行),查出当前批最大id作为下一批起点 - 必须确保
WHERE条件命中索引,用EXPLAIN验证,避免全表扫描锁整张表 - 每批独立
COMMIT,不能包在一个事务里;执行后检查ROW_COUNT(),为 0 立即退出循环 - 禁用
WHERE DATE(create_time) = '2024-01-01'这类函数包裹条件——索引失效,锁表风险高
binlog_cache_size 和 group commit 参数怎么设才不翻车?
binlog_cache_size 是会话级缓冲区,设太小导致频繁落盘,设太大纯属浪费。关键看实际 binlog 体积:
- 查当前值:
SHOW VARIABLES LIKE 'binlog_cache_size';观察是否溢出:SHOW STATUS LIKE 'binlog_cache_disk_use',非零说明已在拖慢提交 - 按单事务平均 binlog 体积估算:比如每行变更生成约 200 字节,处理 10 万行 → 至少预留 20MB → 设为
20971520 -
binlog_group_commit_sync_delay单位是微秒,不是毫秒;从10~100微秒起步,别盲目设成1000(1ms) - 必须保持
innodb_support_xa = ON,关掉它可能造成 binlog 与 InnoDB 数据不一致,尤其开了 GTID 时
拆完事务还得配对并行复制参数
只拆事务不调从库参数,小事务照样串行回放。生产环境必须同步调整:
-
slave_parallel_type = LOGICAL_CLOCK(MySQL 5.7.22+ / 8.0 强制要求),不是DATABASE -
slave_parallel_workers = 4~8:设太高反而增加调度开销,别堆到 16+ -
slave_preserve_commit_order = ON:必须开启,否则主从一致性可能被破坏 - 主库确认已启用组提交:
binlog_group_commit_sync_delay建议设为100000~200000微秒
真正容易被忽略的是断点续跑能力——没人能保证一次跑完。网络抖动、从库临时负载高、某批语句锁等待超时,都可能中断;脚本里必须有重试逻辑和已处理范围记录。











