2、Binlog文件结构解析:物理存储、Magic Number与Event结构

Binlog 文件到底长什么样?这是很多 DBA 和开发同学问我的第一个问题。说实话,我刚开始接触 MySQL 主从复制时,也以为 Binlog 就是个简单的日志文件,每行记录一条 SQL。后来踩了坑才发现——完全不是那么回事。

Binlog 是二进制文件,不是文本文件。你不能用 cat 或者 vim 直接看。它有一套严格的物理存储格式,从文件头到每个事件,都有固定的结构。搞懂这个,你才能真正理解 CDC 是怎么工作的。

2.1 物理存储概览

一个 Binlog 文件,从磁盘角度看,就是一段连续的字节流。我习惯把它分成三个层次:

  • 文件级:Magic Number + 多个 Event 串行排列
  • 事件级:每个 Event 由 Header + Data 组成
  • 字段级:Header 和 Data 内部又有固定偏移的字段

你想想看,这就像一本有固定章节结构的书。文件头是封面,每个事件是一页,页眉记录元信息,正文记录具体数据。

核心要点:Binlog 是 append-only 的写入模式。新事件永远追加到文件末尾,不会修改已写入的事件。这保证了数据捕获的线性顺序。

下面这张图是我自己画的结构总览,建议你多看几遍:

Binlog 文件物理结构总览 Magic Number (4 bytes: 0xfe 0x62 0x69 0x6e) Event 1 (Format Description Event) Header (19 bytes) | Data (可变长度) Event 2 (Table Map Event) Header (19 bytes) | Data (表结构映射信息) Event 3 (Write Rows Event / Update Rows Event / Delete Rows Event) Header (19 bytes) | Data (行变更数据) ...... 每个 Event 长度不固定,由 Header 中的 event_size 字段决定

2.2 Magic Number:文件的身份证

每个 Binlog 文件的前 4 个字节是固定的,叫 Magic Number。它的值是 0xfe 0x62 0x69 0x6e。翻译成 ASCII 字符串就是 \xfe bin。

为什么要有这个东西?说白了,就是让 MySQL 或者解析工具能快速判断:这个文件是不是一个合法的 Binlog。我在项目中遇到过有人误把普通日志文件当成 Binlog 来解析,结果程序直接崩溃。后来我加了个 Magic Number 校验,问题就解决了。

小技巧:用 hexdump -C binlog.000001 | head -1 就能看到前 4 个字节。如果开头不是 fe 62 69 6e,那这个文件肯定有问题。

2.3 Event 结构:Binlog 的基本单元

Magic Number 之后,就是一个个 Event 首尾相连。每个 Event 都遵循同样的结构:

组成部分 大小 说明
Event Header 19 字节(固定) 记录事件类型、时间戳、服务器 ID、事件长度等元信息
Event Data 可变长度 事件的具体负载,不同事件类型格式不同

嗯,这里要注意:Header 永远是 19 字节,这是 MySQL 官方定的标准。Data 的长度由 Header 中的 event_size 字段决定——event_size - 19 就是 Data 的实际大小。

2.4 Event Header 逐字段解析

Header 虽然只有 19 字节,但里面的信息量很大。我把它拆成 6 个字段,每个字段都有明确的偏移位置:

偏移 大小 字段名 说明
0 4 字节 timestamp 事件创建时的 Unix 时间戳(秒)
4 1 字节 type_code 事件类型编号(如 2 表示 QUERY_EVENT)
5 4 字节 server_id 产生该事件的 MySQL 服务器 ID
9 4 字节 event_size 整个 Event 的总长度(Header + Data)
13 4 字节 log_pos 该事件在 Binlog 文件中的起始偏移位置
17 2 字节 flags 标志位(如 LOG_EVENT_BINLOG_IN_USE_F)

我个人觉得,log_pos 这个字段特别重要。它记录了事件在文件中的位置,CDC 工具就是靠它来做断点续传的。我曾经帮一个团队排查数据同步延迟问题,发现他们的解析器没有正确读取 log_pos,导致每次重启都要从头解析——那叫一个慢。

避坑指南:我曾经遇到过 MySQL 5.6 和 5.7 的 Header 解析差异。虽然 Header 结构本身没变,但某些事件类型的 flags 字段在 5.7 中新增了位含义。如果你在解析老版本 Binlog,一定要确认版本兼容性。

2.5 Event Data:负载的多样性

Data 部分没有固定结构,完全取决于 type_code。常见的 Event 类型有几十种,但 CDC 场景下我们最关心的是这几类:

  • FORMAT_DESCRIPTION_EVENT (type_code=15):Binlog 文件的第一个事件,描述服务器版本和 Binlog 格式版本
  • TABLE_MAP_EVENT (type_code=18):记录表 ID 与表结构的映射关系,是解析行事件的前提
  • WRITE_ROWS_EVENT (type_code=30):INSERT 操作的行数据
  • UPDATE_ROWS_EVENT (type_code=31):UPDATE 操作的行数据(包含旧值和新值)
  • DELETE_ROWS_EVENT (type_code=32):DELETE 操作的行数据
  • QUERY_EVENT (type_code=2):DDL 语句或基于语句的 DML
  • XID_EVENT (type_code=16):事务提交标记

你想想看,一个完整的事务在 Binlog 里是什么样子?通常是:

GTID_EVENT (如果有开启 GTID)
  → BEGIN 标记 (QUERY_EVENT)
  → TABLE_MAP_EVENT (表结构映射)
  → WRITE_ROWS_EVENT / UPDATE_ROWS_EVENT / DELETE_ROWS_EVENT (行变更)
  → XID_EVENT (事务提交)

这个顺序是固定的。解析器必须按这个顺序来,否则拿到的数据就是乱的。

2.6 实战:如何手动解析一个 Event

光说不练假把式。我们拿一个真实的 Binlog 片段来走一遍解析流程。假设我用 hexdump 抓到了以下字节(十六进制):

00000000  fe 62 69 6e 00 00 00 00  00 00 00 01 00 00 00 00  |.bin............|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

前 4 个字节 fe 62 69 6e 是 Magic Number,确认是 Binlog 文件。接下来就是第一个 Event 的 Header:

  • timestamp (偏移 0-3):00 00 00 00 → 0,表示时间戳为 0(这是 FORMAT_DESCRIPTION_EVENT 的特殊情况)
  • type_code (偏移 4):0f → 15,确认是 FORMAT_DESCRIPTION_EVENT
  • server_id (偏移 5-8):00 00 00 01 → 1,服务器 ID 为 1
  • event_size (偏移 9-12):00 00 00 00 → 这里需要继续读后面的字节才能确定

嗯,这里要注意:event_size 是 4 字节小端序。如果后面字节是 00 00 00 73,那总长度就是 115 字节,Data 部分就是 115 - 19 = 96 字节。

经验之谈:我建议你在写解析器时,先解析 Header 的 event_size,然后根据这个值跳过整个 Event。这样即使你不关心某些事件类型,也能快速定位到下一个事件。这是高效 CDC 解析器的基本功。

2.7 本章小结

Binlog 文件结构其实不复杂,但细节很多。记住三个关键点:

  1. 文件以 Magic Number 开头,用于校验合法性
  2. 每个 Event 由 19 字节 Header + 可变长度 Data 组成,Header 记录了时间戳、类型、长度、位置等关键信息
  3. Data 格式由 type_code 决定,CDC 场景下重点关注 FORMAT_DESCRIPTION、TABLE_MAP、行事件和 XID 事件

搞懂这些,你就掌握了 Binlog 解析的底层基础。下一层,我们会深入每个事件类型的 Data 格式,看看行数据到底是怎么编码的。