未提交事务不会写入binlog,但会阻塞binlog_cache释放,导致后续事务卡在waiting for handler commit状态;其本质是写入通道被堵死,而非真实binlog积压。

不会导致 Binlog 积压——未提交事务根本不会写入 Binlog,但会阻塞 Binlog cache 的释放,造成后续事务提交卡在 waiting for handler commit 状态,看起来像“Binlog 积压”,实则是写入通道被堵死。
未提交事务不写 Binlog,但卡住 binlog_cache
MySQL 严格遵循两阶段提交(2PC):只有 COMMIT 执行成功后,该事务的 binlog event 才会从会话私有的 binlog_cache 刷出、落盘、并标记为可复制。未提交事务产生的 binlog event 始终滞留在内存(或溢出到磁盘临时文件),既不会出现在 mysqlbinlog 输出里,也不会被从库拉取。
但问题在于:binlog_cache 是每个连接独占的,且不会因事务长时间未提交而自动清理或复用。只要事务开着,这块资源就一直被锁住。
- 大量长事务并发 →
Binlog_cache_disk_use指标飙升(说明频繁写临时文件) - 大事务本身产生海量变更(如
UPDATE百万行)→ 反复撑爆binlog_cache_size→ 持续串行写磁盘临时文件 → IO 拖慢 - 其他正常小事务执行
COMMIT时,必须排队等这个大事务把 binlog cache 写完 → 状态变成waiting for handler commit
如何快速定位“活着但没提交”的事务
别盯着 SHOW BINARY LOGS 或 mysqlbinlog 查积压量——它们根本看不到未提交事务。要查事务本身是否异常存活:
- 查运行超 60 秒的活跃事务:
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state = 'RUNNING' AND TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60 - 关联查对应线程详情:
SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID IN (SELECT trx_mysql_thread_id FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state = 'RUNNING' AND TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60) - 注意:
trx_state = 'RUNNING'不代表正在执行 SQL,哪怕线程状态是Sleep,只要没COMMIT或ROLLBACK,它就一直挂着,持续占用 cache
waiting for handler commit 是关键信号
这个状态不是锁等待,也不是磁盘 IO 等待,而是 InnoDB 已写完 redo log,正卡在等 server 层把 binlog event 从 cache 刷出去。一旦看到多个会话同时卡在这个状态,基本可以断定有大事务正在刷 binlog cache 临时文件。
- 它通常伴随
Binlog_cache_disk_use接近Binlog_cache_use(说明几乎每个事务都在落盘) - 云厂商 RDS 控制台里常表现为“SQL 洞察”中普通 DML 执行时间暴涨,而 CPU/内存无明显压力
- 杀掉那个长事务线程(
KILL [thread_id])后,卡住的COMMIT会立刻恢复 —— 这是最直接的验证方式
调参只是缓兵之计,拆事务才是正解
盲目调大 binlog_cache_size 或 max_binlog_cache_size 只会让单次卡顿更久、更隐蔽,还可能掩盖真实问题:
-
SET GLOBAL binlog_cache_size = 4194304(4MB)可试,但别设 >512MB;连接数多时极易引发内存暴涨 -
max_binlog_cache_size是熔断值,超限直接报ERROR 1197,不是性能优化参数 - 真正该做的是:用
WHERE id BETWEEN ? AND ?或时间范围分批执行,确保每批修改行数可控(如 ≤1000 行) - 拆分后必须保证幂等性(例如
UPDATE ... WHERE status = 0),避免重试导致重复更新
最容易被忽略的一点:即使事务拆小了,如果 sync_binlog 设为 0 或过大,binlog 仍可能堆积在内存里不出去;生产环境建议设为 sync_binlog = 1 或至少 =10,再配合事务拆分才真正有效。











