3、日志解析实战(以MySQL Binlog为例):Binlog的三种格式及选型建议

说到增量同步,Binlog 是绕不开的核心。说白了,MySQL 的主从复制、数据恢复、还有我们这套增量计算方案,底层都靠它。

我记得刚入行那会儿,有个同事把 Binlog 格式配错了,导致从库数据跟主库对不上。排查了整整两天,最后发现是 Statement 格式下某个 UUID() 函数惹的祸。从那以后,我对 Binlog 格式就格外上心。

今天咱们就把 Binlog 的三种格式掰开揉碎讲清楚。你听完就能根据业务场景做出选型判断。

3.1 Binlog 是什么?为什么需要它?

Binlog 是 MySQL 的二进制日志。它记录了对数据库执行更改的所有操作(除了 SELECT 和 SHOW)。

它的核心用途有三个:

  • 主从复制:从库拉取主库的 Binlog 并重放,保持数据一致。
  • 数据恢复:通过 Binlog 回滚到某个时间点。
  • 增量数据捕获:我们做增量同步,就是实时解析 Binlog 中的变更事件。

嗯,这里要注意:Binlog 默认是不开启的。你需要手动设置 log_bin=ON,并指定 server_id。

3.2 三种格式详解

MySQL 提供了三种 Binlog 格式:Statement、Row 和 Mixed。它们各有各的脾气。

3.2.1 Statement 格式

这种格式记录的是 SQL 语句本身。比如你执行了一条 UPDATE,Binlog 里就原样记下这条 SQL。

优点:日志量小,因为一条 SQL 可能影响很多行,但只记一条记录。

缺点:坑比较多。我举几个例子:

  • NOW()、UUID()、RAND() 等非确定性函数,在主从库上执行结果可能不同。
  • 使用了 LIMIT 的 UPDATE 或 DELETE,如果数据顺序不一致,结果也会不一样。
  • 存储过程、触发器、自定义函数,这些在 Statement 格式下容易出问题。
我曾经在项目里遇到过:主库执行 UPDATE t SET name=UUID() WHERE id=1,Binlog 里记的是这条 SQL。从库重放时又生成一个新的 UUID,结果主从数据就不一致了。排查起来非常痛苦。

3.2.2 Row 格式

这种格式记录的是 每一行数据的变化。它会记下修改前和修改后的完整行数据。

优点:精确、安全。不管 SQL 怎么写,最终改了什么行,Binlog 就记什么行。主从数据一致。

缺点:日志量巨大。一条 UPDATE 影响 10 万行,Binlog 里就记 10 万条变更记录。

我个人习惯:生产环境首选 Row 格式。虽然日志量大,但数据一致性是底线。磁盘便宜,数据错了可赔不起。

3.2.3 Mixed 格式

Mixed 是 Statement 和 Row 的混合体。MySQL 自己判断:如果 SQL 是确定性的,就用 Statement 格式;如果包含非确定性函数,就自动切换成 Row 格式。

优点:兼顾了 Statement 的小日志量和 Row 的安全性。

缺点:判断逻辑不透明。有时候你以为它用了 Row,结果它用了 Statement,出了问题你都不知道。

我的建议:如果你对 MySQL 内部机制非常熟悉,可以用 Mixed。否则,老老实实用 Row。你想想看,为了省点磁盘空间,去赌 MySQL 的判断逻辑,不值得。

3.3 三种格式对比

我把它们的核心差异整理成了一张表,方便你对照:

对比维度 Statement Row Mixed
记录内容 SQL 语句 行变更数据 自动切换
日志大小 小 大(可能膨胀 10-100 倍) 中等
数据一致性 低(非确定性函数有风险) 高(一致) 中等(依赖 MySQL 判断)
解析复杂度 低(直接解析 SQL) 高(需要解析行格式) 中等
适用场景 测试环境、日志量敏感 生产环境、增量同步 有一定经验的团队

3.4 选型建议

直接给结论:

  • 生产环境做增量同步:选 Row。没得商量。我们这套课程后续的实战,全部基于 Row 格式。
  • 测试环境或日志量极其敏感:可以考虑 Mixed,但要做好监控,一旦发现数据不一致,立即切回 Row。
  • Statement:除非你非常清楚自己在做什么,否则别碰。我在面试时经常问候选人这个问题,能说清楚 Statement 坑的人,基本功都不差。

3.5 如何查看和修改 Binlog 格式

操作很简单,我直接给命令:

-- 查看当前 Binlog 格式
SHOW VARIABLES LIKE 'binlog_format';

-- 临时修改(当前会话)
SET SESSION binlog_format = 'ROW';

-- 永久修改(需要重启 MySQL)
-- 在 my.cnf 或 my.ini 中添加:
[mysqld]
binlog_format = ROW
server_id = 1
log_bin = /var/log/mysql/mysql-bin.log
注意:修改 binlog_format 后,已有的 Binlog 文件不会变。新生成的 Binlog 才会使用新格式。如果你要做格式切换,建议在业务低峰期操作,并确保从库也能跟上。

3.6 知识体系总览

下面这张图帮你把 Binlog 格式的核心逻辑串起来:

Binlog 三种格式对比与选型流程图 Binlog 格式选型决策流程 Binlog 格式选型 Statement Row ✅ Mixed 特性 日志量小 一致性风险高 不推荐生产 特性 日志量大 数据一致 生产首选 特性 折中方案 依赖 MySQL 判断 需监控 推荐:Row 格式

3.7 实战:解析 Row 格式的 Binlog

既然推荐 Row 格式,那我们就看看它长什么样。MySQL 自带的 mysqlbinlog 工具可以查看 Binlog 内容。

-- 查看 Binlog 文件列表
SHOW BINARY LOGS;

-- 解析指定 Binlog 文件(Row 格式)
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001

-- 只看某个数据库的变更
mysqlbinlog --database=mydb -v mysql-bin.000001

-- 指定时间范围
mysqlbinlog --start-datetime="2025-01-01 00:00:00" --stop-datetime="2025-01-02 00:00:00" -v mysql-bin.000001

输出内容大致是这样的:

# at 1234
#250101 10:00:00 server id 1  end_log_pos 1300 CRC32 0x12345678  Table_map: `mydb`.`users` mapped to number 89
# at 1300
#250101 10:00:00 server id 1  end_log_pos 1400 CRC32 0x9abcdef0  Update_rows: table id 89 flags: STMT_END_F

BINLOG '
...
'/*!*/;
### UPDATE `mydb`.`users`
### WHERE
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2='alice' /* STRING(50) meta=65044 nullable=1 is_null=0 */
### SET
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2='alice_updated' /* STRING(50) meta=65044 nullable=1 is_null=0 */
/*!*/;

看到没?Row 格式下,Binlog 明确记录了 修改前的值(WHERE 部分) 和 修改后的值(SET 部分)。这就是它精确的原因。

我个人习惯:在开发增量同步程序时,我会先用 mysqlbinlog 把 Binlog 解析成可读格式,确认字段映射关系,然后再写代码。这样能避免很多低级错误。

3.8 避坑指南

最后,分享几个我踩过的坑:

  • Binlog 文件不要手动删除:用 PURGE BINARY LOGS 命令,否则会导致主从复制中断。
  • Row 格式下,大事务要拆小:一次更新 100 万行,Binlog 会膨胀到几百 MB,解析和传输都很慢。
  • 注意磁盘空间:Row 格式的 Binlog 增长很快。建议设置 expire_logs_days=7,自动清理过期日志。
  • 从库也要配 Row 格式:主库改了,从库没改,复制会报错。