大事务填满binlog_cache_size后触发串行磁盘写入并阻塞所有其他事务commit:溢出内容写入ml临时文件,需排队等待lock_log锁,导致后续commit卡在waiting for handler commit状态。

大事务填满 binlog_cache_size 后,不是简单“慢一点”,而是会触发串行磁盘写入、阻塞所有其他事务的 COMMIT —— 这是性能断崖式下跌的根源。
binlog cache 内存写满后会发生什么
每个连接独占一块内存缓存(binlog_cache_size),DML 产生的 binlog event 全部先塞进去。一旦写满:
- MySQL 立即创建一个以
ML开头的临时磁盘文件,把溢出内容刷进去 - 这个落盘动作是**串行的**:多个事务同时触发时,必须排队等同一把
LOCK_log锁 - 后续所有事务执行
COMMIT都卡在waiting for handler commit状态,直到大事务的 cache 全部刷完 -
SHOW STATUS LIKE 'binlog_cache_disk_use'会明显跳升,若接近binlog_cache_use,说明几乎每个事务都在刷盘
为什么调大 binlog_cache_size 不一定管用
盲目加内存只是把问题往后推,还可能引发新风险:
- 设太高(比如 > 512MB)会导致单连接内存暴涨,尤其连接数多时容易 OOM
-
max_binlog_cache_size是熔断阀值,超了直接报错ERROR 1197 (HY000),它不加速,只防崩 - 云厂商(如阿里云 RDS)通常锁死该参数,线上根本改不了
- 即使调到 4MB,一个百万行的
UPDATE ... WHERE id BETWEEN 1 AND 1000000(ROW 格式)仍可能撑爆它
真正该盯住的两个指标
别只看 binlog 文件大小或延迟,重点查这两类“活体阻塞源”:
- 长事务本身是否未提交:
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state = 'RUNNING' AND TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60 - 提交卡点是否真实存在:
SHOW PROCESSLIST中状态为waiting for handler commit的线程,基本就是被大事务堵死的受害者
拆分事务比调参更可靠的关键原因
调参治标,拆分治本——因为底层机制决定了并发瓶颈无法靠内存绕过:
- 每个子事务独立申请自己的
binlog_cache_size,互不抢占 - 锁持有时间大幅缩短,
LOCK_log竞争窗口变窄,小事务能快速抢到锁完成提交 - 配合主键/时间戳分片(如
WHERE id BETWEEN ? AND ?),还能避免全表扫描导致的 gap lock 扩散 - 幂等设计下,某批次失败可重试,而大事务一旦中断,只能回滚重来
最常被忽略的一点:binlog_cache_size 是线程级私有内存,不会因事务空闲自动释放。一个 Sleep 着却没提交的事务,照样霸占着 cache 和可能已生成的 ML 临时文件,持续消耗 IO 资源。











