大事务导致Binlog卡住是因为MySQL仅在COMMIT时批量写入binlog,事务未提交前所有event滞留内存,从库无法接收;同时长事务持有行锁或MDL锁阻塞其他操作。安全拆分需基于主键或时间戳分段、确保范围不重叠不遗漏且具备幂等性。

Binlog刷盘慢不是因为INSERT本身慢,而是事务太大、COMMIT太晚,导致binlog事件积压在内存里不出去;必须拆小事务+调准sync_binlog,否则从库永远收不到日志。
为什么大事务会让Binlog卡住
MySQL只在COMMIT时才把整个事务的binlog event一次性写入文件。一个改了10万行的事务,哪怕执行完了,binlog也还在内存buffer里憋着——主库看着“成功”,但从库连第一个event都收不到。同时这个长事务还长期持有行锁或MDL锁,其他DML/DDL全被堵住。
常见错误现象:SHOW PROCESSLIST里看到大量线程卡在commit状态;监控指标Innodb_log_waits > 0或Seconds_Behind_Master持续飙升;SHOW BINARY LOGS显示日志文件大小增长极慢。
怎么安全拆分事务避免Binlog堆积
不能靠LIMIT随便分页,必须保证数据范围不重叠、不遗漏,且支持重试:
- 用自增主键或时间戳字段切分,例如
WHERE id BETWEEN ? AND ?或WHERE create_time >= ? AND create_time - 每次执行前先查出本批最大
id(或create_time),作为下一批起点,防止并发插入漏数据 - 禁用
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE混用,它们可能隐式改变主键值,破坏分片连续性 - 每批控制在500–2000行,太小没收益,太大易触发锁升级、undo日志膨胀、主从延迟加剧
sync_binlog和innodb_flush_log_at_trx_commit怎么配才不丢又不卡
这两个参数必须协同调优,单改一个效果有限,甚至更卡:
-
sync_binlog = 0:binlog完全不刷盘,风险极高,仅限可重跑的ETL中间表场景 -
sync_binlog = 1000+innodb_flush_log_at_trx_commit = 2:主流折中方案,崩溃最多丢1秒事务,binlog每1000个事务刷一次盘,吞吐提升明显 -
sync_binlog = 1+innodb_flush_log_at_trx_commit = 1:最安全,但性能最差,仅建议核心交易表保留 - 执行完必须立刻切回原值,尤其云数据库(如阿里云RDS)不支持动态改
sync_binlog,得提前规划
事务里别混其他操作,锁范围会悄悄变大
很多人以为“只是插数据”,结果事务里夹了个SELECT FOR UPDATE或UPDATE,锁就从几行扩大到整范围:
- 避免在批量INSERT事务中执行
SELECT或UPDATE,哪怕只是查个计数 - 二级索引条件(如
WHERE idx_col = 'abc')会加gap lock,锁住一大片空隙,实际影响远超匹配行 - 唯一索引+等值查询(如
WHERE pk = 123)才是最小锁粒度,但也要确认没被优化器误判为范围扫描 - 别用
OR、函数包裹字段(如WHERE DATE(create_time) = '2024-01-01'),这些让索引失效,直接升表锁
真正难的是分片逻辑的幂等性和边界处理——比如时间戳字段有重复值、主键跳号、凌晨跨天写入,这些地方一错,轻则漏数据,重则主从不一致。别只盯着SQL怎么写,先想清楚“这批数据到底该归谁管”。


















