批量 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 或根分区使用率异常上涨的情况,但无法定位到具体进程。
排查顺序
- 先看状态变量:
Binlog_cache_disk_use的增长速度是否与业务批量任务的时间点吻合。 - 再看 tmpdir:
ls -lh /tmp/ML*看临时文件的数量与体积,同时用lsof确认持有进程是 mysqld。 - 最后看大事务:慢日志里执行时间长、影响行数大的
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 更有效。
参考文档: