3、Binlog 文件结构:理解 Binlog 文件的物理结构(Magic Number、Event、Rotate Event)
上一章我们聊了 Binlog 的逻辑概念和三种格式。今天,咱们把袖子撸起来,直接看看 Binlog 文件在磁盘上到底长什么样。
说白了,Binlog 就是一个二进制文件,但它不是乱糟糟的字节堆砌。MySQL 给它定了一套严格的物理结构规范。你只要掌握了这套规范,就能像读一本结构清晰的书一样,读懂任何 Binlog 文件。
3.1 文件的开头:Magic Number
每个 Binlog 文件的头 4 个字节,固定是 0xfe 0x62 0x69 0x6e。这玩意儿叫 Magic Number,也叫魔数。
你把它转成 ASCII 看看:0xfe 不是可打印字符,但后面三个字节 0x62 0x69 0x6e 对应的是 'b' 'i' 'n'。合起来就是 \xfe 'b' 'i' 'n'。
MySQL 为什么这么设计?我个人理解,这就像书的扉页。你拿到一个文件,先读前 4 个字节。如果匹配,基本能确认这是个 Binlog 文件。如果不匹配,那肯定不是,直接报错退出,省得后面解析出一堆乱码。
hexdump -C mysql-bin.000001 | head -n 1 命令,亲眼看看这 4 个字节。我每次排查 Binlog 损坏问题时,第一件事就是检查 Magic Number 是否完整。
3.2 核心单元:Event
Magic Number 之后,整个文件就是由一个个 Event 首尾相连组成的。Event 是 Binlog 的最小逻辑单元,没有之一。
你想想看,一条 INSERT 语句怎么记录?它会被包装成一个或多个 Event。一个事务怎么记录?它是一组 Event 的集合。Binlog 文件的切换怎么记录?也是一个特殊的 Event。
每个 Event 的结构分为两部分:
- Event Header(事件头):固定 19 字节,描述这个 Event 的元信息。
- Event Body(事件体):变长,存放真正的数据。
3.2.1 Event Header 详解
这 19 个字节,每个字段都有讲究。我整理了一张表,你对照着看:
| 偏移量(字节) | 长度(字节) | 字段名 | 说明 |
|---|---|---|---|
| 0 | 4 | timestamp | 事件创建时的 Unix 时间戳 |
| 4 | 1 | type_code | 事件类型编号(如 FORMAT_DESCRIPTION_EVENT = 15) |
| 5 | 4 | server_id | 产生该事件的服务器 ID |
| 9 | 4 | event_length | 整个 Event 的总长度(Header + Body) |
| 13 | 4 | next_position | 下一个 Event 在文件中的起始偏移量 |
| 17 | 2 | flags | 标志位,如 LOG_EVENT_BINLOG_IN_USE_F |
这里有个字段特别重要——next_position。它直接告诉你下一个 Event 从哪里开始。解析 Binlog 时,你读完当前 Event,直接跳到 next_position 继续读就行。这种设计让 Binlog 的解析天然支持流式处理,不需要随机跳转。
type_code 决定了你如何解析后面的 Event Body。不同的 type_code,Body 的结构完全不同。MySQL 官方定义了 30 多种 Event 类型,但日常工作中常用的也就十几种。
3.2.2 常见的 Event 类型
我挑几个你一定会遇到的 Event 类型说说:
- FORMAT_DESCRIPTION_EVENT (15):每个 Binlog 文件的第二个 Event(第一个是 Rotate Event)。它描述了 MySQL 服务器的版本、Binlog 版本号、以及所有 Event 类型的 Header 长度。没有它,你连后面的 Event 都解析不了。
- QUERY_EVENT (2):记录 DDL 语句(如 CREATE TABLE)或事务开始的 BEGIN 语句。
- TABLE_MAP_EVENT (19):行模式下的关键 Event。它把表名、列信息映射成一个 ID,后面的 Rows Event 通过这个 ID 找到对应的表结构。
- WRITE_ROWS_EVENT (30) / UPDATE_ROWS_EVENT (31) / DELETE_ROWS_EVENT (32):行模式下,真正记录数据变更的 Event。
- XID_EVENT (16):标记一个事务的提交。
嗯,这里要注意:type_code 的值在不同 MySQL 版本间是稳定的,但 Event Body 的格式可能会微调。所以解析时一定要先读 FORMAT_DESCRIPTION_EVENT,拿到版本信息再决定怎么解析后面的 Event。
3.3 特殊的 Event:Rotate Event
Rotate Event 是个特殊的存在。它通常出现在两个地方:
- Binlog 文件的第一个 Event:告诉解析器,上一个 Binlog 文件叫什么名字,以及上一个文件写到了哪个位置。
- Binlog 文件的最后一个 Event:当 Binlog 文件达到
max_binlog_size或 MySQL 重启时,写入一个 Rotate Event,指向下一个 Binlog 文件。
Rotate Event 的 Body 结构很简单:
- 前 8 个字节:上一个 Binlog 文件写到的位置(position)。
- 后面跟着:下一个 Binlog 文件的文件名(以
\0结尾)。
我曾经在做一个 Binlog 实时同步工具时,遇到过一个坑:MySQL 重启后,新的 Binlog 文件第一个 Event 是 Rotate Event,但它的 next_position 指向的是文件开头(即 Magic Number 之后)。如果解析器没处理好这个边界情况,就会死循环。所以,解析 Rotate Event 时,一定要特殊处理,不能按普通 Event 的逻辑来跳转。
next_position 字段。你应该直接跳到 Rotate Event Body 中指定的 position 值,或者跳到下一个文件的起始位置。我曾经在这个问题上折腾了一整天,最后翻源码才找到原因。
3.4 Binlog 文件的整体布局
把上面这些拼起来,一个 Binlog 文件的物理结构就是这样的:
+---------------------+
| Magic Number (4B) | -- 0xfe 0x62 0x69 0x6e
+---------------------+
| Rotate Event | -- 第一个 Event,指向上一个文件
+---------------------+
| Format Description | -- 描述 Binlog 版本和 Event 格式
| Event |
+---------------------+
| Table Map Event | -- 行模式:映射表结构
+---------------------+
| Rows Event | -- 行模式:实际数据变更
+---------------------+
| Xid Event | -- 事务提交
+---------------------+
| ... | -- 更多 Event
+---------------------+
| Rotate Event | -- 最后一个 Event,指向下一个文件
+---------------------+
注意,第一个 Rotate Event 和最后一个 Rotate Event 的用途不同。第一个是为了让解析器知道「我从哪里来」,最后一个是为了告诉解析器「我要去哪里」。
3.5 一张图看懂 Binlog 文件结构
光说可能不够直观,我画了一张图,把整个 Binlog 文件的物理结构和 Event 的内部结构都展示出来了:
3.6 动手验证一下
光看理论不过瘾,咱们来点实际的。你可以用 mysqlbinlog 工具直接解析 Binlog 文件,看看 Event 长什么样:
# 查看 Binlog 文件列表
SHOW BINARY LOGS;
# 用 mysqlbinlog 解析指定文件
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001 | head -50
输出里你会看到类似这样的内容:
# at 4
#240101 10:00:00 server id 1 end_log_pos 123 Rotate to mysql-bin.000002 pos: 4
# at 123
#240101 10:00:00 server id 1 end_log_pos 456 Format_desc
# at 456
#240101 10:00:01 server id 1 end_log_pos 789 Table_map: `test`.`users`
# at 789
#240101 10:00:01 server id 1 end_log_pos 1024 Write_rows: table id 123 flags: STMT_END_F
看到 # at 4 了吗?这个 4 就是 Event 在文件中的起始偏移量。为什么从 4 开始?因为前 4 个字节是 Magic Number。第一个 Event 从偏移量 4 开始,正好对应我们前面讲的 Rotate Event。
我个人习惯用 end_log_pos 来跟踪解析进度。这个值就是 Event Header 里的 next_position。你对比一下,# at 4 的 end_log_pos 是 123,下一个 Event 的 # at 123,完全吻合。
1. 文件以 4 字节 Magic Number 开头。
2. 后面全是 Event,每个 Event 由 19 字节 Header 和变长 Body 组成。
3. Rotate Event 负责文件间的衔接,是解析器切换文件的关键信号。