1、Binlog 初探:什么是 Binlog?Binlog 的三种格式对比与选型

说实话,我刚接触 MySQL 那会儿,对 Binlog 的理解就是「一个记录变更的日志文件」。直到有一次线上数据被误删,我靠着 Binlog 把整张表恢复到了误操作前 1 秒的状态——从那以后,我再也不敢小看它了。

Binlog,全称 Binary Log,是 MySQL 的二进制日志。它记录的是数据库的所有变更操作,比如 INSERT、UPDATE、DELETE,还有表结构变更(DDL)。注意,SELECT 查询是不会被记录的。

一句话总结:Binlog 就是 MySQL 的「黑匣子」,记录了数据库的每一次「写操作」。

1.1 Binlog 到底长什么样?

Binlog 不是文本文件,你用 cat 打开会看到一堆乱码。它有自己的格式,需要用 MySQL 自带的 mysqlbinlog 工具来解析。

我习惯把 Binlog 理解成「事件流」。每个事件代表一次操作,比如:

  • Query 事件:记录一条 SQL 语句
  • Write_rows 事件:记录插入的行数据
  • Update_rows 事件:记录更新前后的行数据
  • Delete_rows 事件:记录删除的行数据
  • Xid 事件:记录事务提交

每个事件都有固定的结构:事件头 + 事件体。事件头里包含时间戳、事件类型、服务器 ID、文件偏移量等信息。事件体就是具体的操作数据。

你可以用这条命令查看 Binlog 内容:

mysqlbinlog --no-defaults -v --base64-output=DECODE-ROWS mysql-bin.000001

嗯,这里要注意:--base64-output=DECODE-ROWS 这个参数很关键,不加的话 ROW 格式的 Binlog 会显示成 base64 编码,根本看不懂。

1.2 Binlog 的三种格式

MySQL 支持三种 Binlog 格式:STATEMENT、ROW、MIXED。选哪种格式,直接决定了你的同步方案怎么做。

我的建议:新项目直接选 ROW 格式。原因后面会讲,但你先记住这个结论。

1.2.1 STATEMENT 格式

STATEMENT 格式记录的是 SQL 语句本身。比如你执行了 UPDATE user SET age=age+1 WHERE id=1,Binlog 里就原样记录这条 SQL。

优点:

  • 日志量小,一条 SQL 可能只占几十字节
  • 解析简单,直接拿到 SQL 就能重放

缺点:

  • 依赖上下文环境。比如用了 NOW()、RAND()、UUID() 这些非确定性函数,在主从库上执行结果可能不一样
  • 存储过程、触发器、自定义函数容易出问题
  • 并发场景下,锁和事务顺序可能导致数据不一致

我曾经在一个老项目里遇到过:主库执行了 DELETE FROM t LIMIT 1,从库重放时因为数据顺序不同,删掉了另一条记录。这就是 STATEMENT 格式的典型坑。

1.2.2 ROW 格式

ROW 格式记录的是每一行数据的变化。比如你更新了一行数据,Binlog 里会记录更新前的完整行和更新后的完整行。

优点:

  • 最精确,每一行数据的变化都清清楚楚
  • 不受函数、存储过程影响,主从数据一致
  • 支持数据恢复,可以精确恢复到某个时间点

缺点:

  • 日志量大。一次 UPDATE 影响 10 万行,Binlog 就会记录 10 万行的前后镜像
  • 解析成本高,需要知道表结构才能还原数据

注意:ROW 格式的 Binlog 默认是「镜像模式」,会记录所有列的值。你可以通过 binlog_row_image 参数控制:FULL(全部列)、MINIMAL(只记录必要列)、NOBLOB(不记录 BLOB 列)。生产环境我一般用 MINIMAL,能省不少空间。

1.2.3 MIXED 格式

MIXED 格式是 STATEMENT 和 ROW 的混合体。MySQL 自己判断:如果 SQL 是确定性的,就用 STATEMENT 记录;如果 SQL 可能产生歧义,就自动切换成 ROW 格式。

优点:

  • 兼顾了 STATEMENT 的小体积和 ROW 的准确性
  • 大部分场景下表现不错

缺点:

  • 不可控。你没法预测 MySQL 什么时候切换格式
  • 解析逻辑复杂,同步工具需要同时支持两种格式
  • 某些边界场景下,MySQL 的判断可能不准确

1.3 三种格式对比

我把三种格式的核心差异整理成了表格,方便你对比:

对比维度 STATEMENT ROW MIXED
记录内容 SQL 语句 行数据变化 混合
日志体积 小 大 中等
数据一致性 低 高 中
解析复杂度 低 高 中
适用场景 简单同步、日志量敏感 数据一致性要求高、实时同步 通用场景
数据恢复能力 弱 强 中

1.4 格式选型建议

你想想看,现在做实时同步,最怕什么?最怕数据不一致。STATEMENT 格式虽然省空间,但坑太多。MIXED 格式看似完美,但不可控。

我个人习惯是这样选的:

  • 新项目、实时同步场景:直接上 ROW 格式,配合 binlog_row_image=MINIMAL。虽然日志量大一点,但数据一致性有保障
  • 老项目迁移:如果原来用的是 STATEMENT 或 MIXED,建议先评估数据一致性风险,再决定是否切换
  • 日志量敏感的场景:比如磁盘空间有限,可以考虑 MIXED 格式,但要做好监控和应急方案

一个小技巧:你可以用 SHOW BINARY LOGS; 查看当前有哪些 Binlog 文件,用 SHOW BINLOG EVENTS IN 'mysql-bin.000001'; 查看具体事件。这两个命令在排查问题时非常有用。

1.5 Binlog 的核心流程

为了让你更直观地理解 Binlog 的工作流程,我画了一张图:

Binlog 生成与同步核心流程 客户端 MySQL Server 执行引擎 Binlog 缓冲区 Binlog 文件 从库 / 同步工具 SQL 请求 写入事件 事务提交时刷盘 Dump 线程推送 说明:Binlog 在事务提交时写入磁盘,从库或同步工具通过 Dump 线程拉取 Binlog 事件

流程其实不复杂:客户端发 SQL 给 MySQL Server,Server 交给执行引擎处理,引擎把变更事件写入 Binlog 缓冲区。事务提交时,缓冲区的内容刷到磁盘上的 Binlog 文件。然后从库或同步工具通过 Dump 线程拉取这些事件,重放到目标库。

这里有个关键点:Binlog 是在事务提交时写入的,不是每执行一条 SQL 就写一次。所以如果事务很大,Binlog 文件可能会突然暴涨。我在项目中遇到过一个大事务导致 Binlog 瞬间写满磁盘的情况,从那以后我养成了监控 Binlog 文件大小的习惯。

1.6 如何查看当前 Binlog 格式

你可以用这条命令查看当前数据库的 Binlog 格式:

SHOW VARIABLES LIKE 'binlog_format';

输出结果类似:

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+

如果想修改格式,可以在 my.cnf 配置文件中设置:

[mysqld]
binlog_format = ROW
binlog_row_image = MINIMAL
expire_logs_days = 7
max_binlog_size = 1G

改完配置记得重启 MySQL 服务。不过要注意,修改 Binlog 格式只影响后续生成的 Binlog,已经存在的 Binlog 文件格式不会变。

避坑指南:我曾经在线上直接执行 SET GLOBAL binlog_format = 'ROW';,结果发现已有的连接还是用旧格式。因为 binlog_format 是会话级变量,新连接才会生效。正确的做法是改配置文件后重启,或者等旧连接断开。

好了,这一章我们聊了 Binlog 是什么、三种格式的区别、以及怎么选型。记住一句话:ROW 格式是实时同步的基石。后面的章节,我们会深入解析 ROW 格式的 Binlog 事件结构,以及如何用代码解析它。