编程 批量 UPDATE 撑大 binlog cache:临时文件、1197 报错与排查顺序

2026-10-19 21:06:02

批量 UPDATE 撑大 binlog cache:临时文件、1197 报错与排查顺序

一次跑几十万行的批量 UPDATE 或 DELETE 放在单个事务里,很容易出现一种不容易发现的现象:磁盘空间被逐渐占满,但表数据并没有明显增长。问题通常出在 binlog cache 这个会话级缓冲区上。

binlog cache 是会话级的,默认 32K

每个向 binary log 写入事件的会话,都会使用一份自己的 binary log cache,大小由 binlog_cache_size 控制,默认 32768 字节。当一个事务产生的 binlog 事件超过这份缓存的大小,MySQL 不会让事务失败,而是把事件写进 tmpdir 下的临时文件。事务越大,临时文件越大,直到事务提交或回滚才释放。

这个参数是会话级的,不是全局共享缓冲区,因此并发写入时,每个大事务都可能各占一份磁盘临时文件。

读两个状态变量:溢出比例

SHOW GLOBAL STATUS LIKE 'Binlog_cache%';
  • Binlog_cache_use:使用 binlog cache 的事务数。
  • Binlog_cache_disk_use:超过 binlog_cache_size、改用磁盘临时文件的事务数。

单看绝对值意义不大,关键看比值:

SELECT
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME='Binlog_cache_disk_use')
  /
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME='Binlog_cache_use') AS disk_use_ratio;

disk_use / use 偏高,说明相当比例的事务都发生了溢出。这两个计数器是自实例启动以来累计的,判断当前趋势时应取两次采样做差值,不要直接看总量。

max_binlog_cache_size 与 1197

max_binlog_cache_size 限制的是单个会话在一个事务中累计需要的 binlog 存储量,而不是缓存本身的大小上限。64 位平台上默认值极大(18446744073709547520 字节),基本等于不限制。

超过它时会直接报错,事务失败:

ERROR 1197 (HY000): Multi-statement transaction required more than
'max_binlog_cache_size' bytes of storage; increase this mysqld variable and try again.

注意这里的语义:它按会话内累计计数,日志事件超出缓存后即使写入临时文件,也依然计入这个上限。所以调大 binlog_cache_size 能减少磁盘临时文件,但不能让你绕过 1197;真正决定 1197 的是 max_binlog_cache_size。

临时文件在 tmpdir

溢出的事件落在 tmpdir 下,默认是 /tmp:

SELECT @@tmpdir;

目录里会出现形如 MLxxxx 的文件。这些文件由正在执行的大事务持有,事务不结束就不释放。/tmp 常常同时被其他进程使用,所以很容易出现在 df 里 /tmp 或根分区使用率异常上涨的情况,但无法定位到具体进程。

排查顺序

  1. 先看状态变量:Binlog_cache_disk_use 的增长速度是否与业务批量任务的时间点吻合。
  2. 再看 tmpdir:ls -lh /tmp/ML* 看临时文件的数量与体积,同时用 lsof 确认持有进程是 mysqld。
  3. 最后看大事务:慢日志里执行时间长、影响行数大的 UPDATE/DELETE;也可以用 performance_schema 定位当前长事务,用 SHOW ENGINE INNODB STATUS 查看 history list length 是否在同步膨胀。

治理手段

按主键分批提交,把单事务行数压到可控范围:

-- 每批 5000 行,循环执行直到 affected rows = 0
UPDATE big_table
SET status = 1
WHERE id > ? AND id <= ?
  AND status = 0
LIMIT 5000;

按主键区间走,避免 LIMIT 配合无索引条件时的全表扫描与锁范围扩大。

评估 binlog_row_image。默认是 FULL,记录变更前后的完整行镜像;改成 minimal 只记录必要的前后镜像,能显著缩小行事件体积,降低溢出概率。代价是依赖完整行镜像的 CDC 工具(如部分基于 binlog 的同步组件)可能拿不到需要的字段,切换前要确认下游行为。

避免在一个事务里既改大表又改多表。跨表事务会把多张表的事件累加到同一份 cache 和同一个会话计数里,还会延长元数据锁的持有时间。

关注长事务的连带影响。大事务不只是 binlog 问题:它会撑大 InnoDB undo,推高 history list length;在从库上重放时形成大事务,放大复制延迟;同时长时间持有 MDL。分批提交对这些都有缓解。

最后,把 Binlog_cache_disk_use 的增长速率纳入监控,比事后清 /tmp 更有效。

参考文档:

复制全文 生成海报 MySQL 运维 binlog 事务

推荐文章

程序员茄子在线接单