4. MySQL Binlog 原理:Binlog 格式与日志结构解析
Binlog,说白了就是 MySQL 的“操作日记”。
它记录了所有改变数据库内容的操作——INSERT、UPDATE、DELETE,以及表结构变更。CDC 管道要捕捉增量数据,Binlog 就是最核心的数据源。我个人习惯把 Binlog 比作飞机的黑匣子:平时你可能不太关注它,但一旦需要排查问题或做数据同步,你就得靠它还原现场。
4.1 Binlog 的三种格式
MySQL 提供了三种 Binlog 格式:STATEMENT、ROW 和 MIXED。它们各有各的脾气,选错了格式,后面 CDC 管道跑起来会非常痛苦。
| 格式 | 记录方式 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | 记录执行的 SQL 语句 | 日志量小,易于阅读 | 非确定性函数(如 NOW()、UUID())会导致主从不一致 |
| ROW | 记录每一行数据的变化(前镜像 + 后镜像) | 精确、安全,CDC 解析最友好 | 日志量大,尤其批量操作时 |
| MIXED | 混合模式:默认用 STATEMENT,遇到非确定性语句自动切换为 ROW | 兼顾日志量和安全性 | 切换逻辑对 CDC 解析不够透明 |
我的建议:做 CDC 增量同步,直接选 ROW 格式。别犹豫。
为什么?因为 ROW 格式记录了每一行数据变更的“前值”和“后值”。你解析 Binlog 时,可以直接拿到完整的字段内容,不需要回查数据库。STATEMENT 格式虽然日志小,但解析 SQL 语句再回放,不确定性太大。
避坑指南:我曾经接手过一个项目,生产环境用的是 STATEMENT 格式。结果有一次 UPDATE 语句里用了 LIMIT,主库和从库更新的行数不一样,数据直接对不齐。从那以后,我所有 CDC 项目都强制要求 ROW 格式。
4.2 Binlog 开启配置
开启 Binlog 其实就改几个参数。但很多人踩过坑——改完配置忘了重启,或者日志文件撑爆磁盘。
在 MySQL 配置文件(my.cnf 或 my.ini)的 [mysqld] 段下,加上这几行:
[mysqld]
# 开启 Binlog
log_bin = mysql-bin
# 指定 Binlog 格式(CDC 必选 ROW)
binlog_format = ROW
# 每个 Binlog 文件的最大大小(单位:字节,默认 1G)
max_binlog_size = 1G
# Binlog 保留天数(根据磁盘空间调整)
expire_logs_days = 7
# 可选:指定要记录的数据库(留空表示全部)
# binlog_do_db = your_database
# 可选:指定不记录的数据库
# binlog_ignore_db = mysql
改完后,重启 MySQL 服务。验证是否生效:
mysql> SHOW VARIABLES LIKE 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
mysql> SHOW VARIABLES LIKE 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
注意:修改 binlog_format 需要重启 MySQL。如果你用的是云数据库(如 RDS),通常可以在控制台直接修改参数组,但也要触发重启。我建议在业务低峰期操作,避免影响线上服务。
4.3 Binlog 日志结构解析
Binlog 不是纯文本文件,它是二进制格式。你直接用 cat 打开会看到一堆乱码。MySQL 提供了 mysqlbinlog 工具来解析它。
先看一个 ROW 格式的 Binlog 事件片段:
# at 219
#250101 10:00:00 server id 1 end_log_pos 315 CRC32 0x12345678 Table_map: `test`.`users` mapped to number 89
# at 315
#250101 10:00:00 server id 1 end_log_pos 400 CRC32 0x9abcdef0 Write_rows: table id 89 flags: STMT_END_FIL
### INSERT INTO `test`.`users`
### SET
### @1=1001 /* INT meta=0 nullable=0 is_null=0 */
### @2='Alice' /* VARSTRING(100) meta=100 nullable=0 is_null=0 */
### @3='alice@example.com' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
这里面有几个关键信息:
- 位置(at 219):事件在 Binlog 文件中的偏移量。CDC 管道就是靠这个位置来断点续传的。
- 时间戳:事件发生的时间。
- server id:哪个 MySQL 实例产生的。主从复制时靠这个区分来源。
- end_log_pos:下一个事件的起始位置。
- 事件类型:
Table_map映射表结构,Write_rows表示插入行。 - 字段值:
@1表示第 1 列,后面跟着实际数据。
4.4 Binlog 事件类型与结构
Binlog 里的事件类型很多,但 CDC 管道最关心的是这几种:
| 事件类型 | 含义 | CDC 处理方式 |
|---|---|---|
TABLE_MAP |
映射表 ID 到表结构 | 解析表名、列名、列类型 |
WRITE_ROWS |
INSERT 操作 | 提取后镜像(新增的行数据) |
UPDATE_ROWS |
UPDATE 操作 | 提取前镜像和后镜像(修改前后的数据) |
DELETE_ROWS |
DELETE 操作 | 提取前镜像(被删除的行数据) |
ROTATE |
Binlog 文件切换 | 更新当前读取的文件名和位置 |
FORMAT_DESCRIPTION |
Binlog 文件头信息 | 校验 Binlog 版本和服务器信息 |
每个事件的结构大致如下:
+---------------------+
| Event Header | ← 固定 19 字节:时间戳、事件类型、server id、end_log_pos 等
+---------------------+
| Event Body | ← 变长:具体数据(行数据、表映射信息等)
+---------------------+
| CRC32 Checksum | ← 可选(MySQL 5.6+ 默认开启)
+---------------------+
经验之谈:解析 Binlog 时,TABLE_MAP 事件和 WRITE_ROWS / UPDATE_ROWS / DELETE_ROWS 事件是成对出现的。先有 TABLE_MAP 告诉你表结构,再有行事件告诉你具体数据。我见过有人只解析行事件,结果拿不到列名,数据对不上号。
4.5 Binlog 整体结构图
下面这张图展示了 Binlog 文件的整体结构,以及 CDC 管道如何消费它:
4.6 实战:用 mysqlbinlog 查看 Binlog
你可以在服务器上直接跑 mysqlbinlog 命令,看看 Binlog 里到底长什么样:
# 查看某个 Binlog 文件的内容(ROW 格式,带详细注释)
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001
# 只看某个时间范围的事件
mysqlbinlog --start-datetime="2025-01-01 10:00:00" --stop-datetime="2025-01-01 11:00:00" mysql-bin.000001
# 只看某个位置之后的事件(断点续传时常用)
mysqlbinlog --start-position=219 mysql-bin.000001
注意:如果你用 ROW 格式,不加 --base64-output=DECODE-ROWS -v 参数,看到的是一堆 Base64 编码的乱码。加上 -v 才会显示可读的 SQL 注释。我第一次用 mysqlbinlog 时没加这个参数,还以为 Binlog 坏了。
4.7 总结一下
Binlog 是 CDC 管道的基石。你只要记住三件事:
- 格式选 ROW,别给自己找麻烦。
- 配置要合理,日志保留天数、文件大小、磁盘空间都得算清楚。
- 结构要搞懂,事件类型、位置偏移、表映射,这些是解析 Binlog 的核心。
嗯,这一章的内容就到这儿。Binlog 的原理搞清楚了,后面搭建 CDC 管道时你就能理解每一步在做什么。