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 的解析天然支持流式处理,不需要随机跳转。

核心要点: Event Header 的 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 是个特殊的存在。它通常出现在两个地方:

  1. Binlog 文件的第一个 Event:告诉解析器,上一个 Binlog 文件叫什么名字,以及上一个文件写到了哪个位置。
  2. 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 的逻辑来跳转。

避坑指南: 解析 Rotate 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 的内部结构都展示出来了:

Binlog 文件物理结构总览 Binlog 文件 (mysql-bin.000001) Magic Number (4B) Rotate Event (第一个) Format Description Event 更多 Event ... 每个 Event 的内部结构 Event Header (19B) + Event Body (变长) Rotate Event (最后一个) 下一个 Binlog 文件 Event Header (19 字节) 展开 timestamp type server_id event_len next_pos flags

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,完全吻合。

总结一下: Binlog 文件结构其实不复杂。记住三个关键点:
1. 文件以 4 字节 Magic Number 开头。
2. 后面全是 Event,每个 Event 由 19 字节 Header 和变长 Body 组成。
3. Rotate Event 负责文件间的衔接,是解析器切换文件的关键信号。