3. MySQL Binlog 基础:Binlog 格式与模式、Row-Based 与 Statement-Based 区别、GTID 模式配置、Binlog 清理策略
聊 Debezium,绕不开 MySQL Binlog。说白了,Debezium 就是靠“蹭”Binlog 来捕获数据变更的。你如果对 Binlog 不熟,那后面配置 Debezium 肯定会踩坑。我这些年帮团队排查过不少 CDC 问题,十有八九都跟 Binlog 设置有关。今天咱们就把这块彻底捋清楚。
3.1 Binlog 是什么?为什么 Debezium 离不开它?
Binlog,全称 Binary Log,是 MySQL 的二进制日志。它记录了所有对数据库的写操作——INSERT、UPDATE、DELETE,以及 DDL 语句。注意,SELECT 是不会被记录的。
Debezium 的工作原理,就是把自己伪装成一个 MySQL Slave,向 Master 请求 Binlog 流。Master 把 Binlog 事件推过来,Debezium 解析后转成结构化的变更事件,再发给 Kafka。所以,没有 Binlog,就没有 Debezium。
核心要点:Debezium 必须依赖 MySQL 开启 Binlog,且格式必须设置为 ROW。否则,你连数据都抓不到。
3.2 Binlog 的三种格式:ROW、STATEMENT、MIXED
MySQL 支持三种 Binlog 格式。我建议你直接记住一句话:生产环境只用 ROW 模式。为什么?咱们一个个看。
| 格式 | 记录方式 | 优点 | 缺点 | Debezium 兼容性 |
|---|---|---|---|---|
| STATEMENT | 记录 SQL 语句本身 | 日志量小,写入快 | 依赖上下文,主从可能不一致 | ❌ 不兼容 |
| ROW | 记录每一行数据变更前后的值 | 精确、安全、主从一致 | 日志量大,尤其大事务 | ✅ 必须使用 |
| MIXED | MySQL 自动判断,混合使用 | 折中方案 | 不可控,Debezium 无法解析 | ❌ 不推荐 |
3.3 ROW 模式 vs STATEMENT 模式:到底差在哪?
我举个例子你就明白了。
假设你执行了一条 SQL:
UPDATE users SET status = 1 WHERE created_at < '2023-01-01';
这条语句影响了 10 万行数据。
- STATEMENT 模式:只记录这条 SQL 文本。日志很小,但问题来了——如果主库和从库的索引选择不一样,或者用了 NOW()、RAND() 等非确定性函数,主从数据就可能不一致。我在项目中遇到过因为 STATEMENT 模式导致主从数据对不上的情况,排查起来非常痛苦。
- ROW 模式:记录每一行变更前后的完整数据。10 万行,就记录 10 万条变更事件。日志量大,但每一行都是精确的。Debezium 拿到这些事件后,能精确知道哪一行从什么值变成了什么值。
我的建议:别纠结,直接上 ROW 模式。日志量大?那是磁盘和网络的问题,不是格式的问题。你可以通过调整 Binlog 保留时间和文件大小来控制磁盘占用。
3.4 GTID 模式:为什么 Debezium 强烈推荐?
GTID,全称 Global Transaction Identifier,全局事务标识符。每个事务在提交时,MySQL 会分配一个全局唯一的 ID,格式像这样:d7b8c9a0-1e2f-3a4b-5c6d-7e8f9a0b1c2d:12345。
没有 GTID 的时候,Debezium 靠文件名 + 偏移量(binlog filename + position)来定位读取位置。这有什么问题?
- 如果 MySQL 主从切换了,新的 Master 可能 Binlog 文件名变了,偏移量对不上。
- 如果 Binlog 被清理了,Debezium 就断了,得重新做全量快照。
有了 GTID,Debezium 只需要记住事务的 GTID,不管 MySQL 怎么切换,它都能精确地从断点处继续读取。说白了,GTID 让 Debezium 的断点续传能力上了一个台阶。
注意:GTID 模式要求 MySQL 5.7 及以上版本。如果你还在用 MySQL 5.6,建议先升级。另外,开启 GTID 后,MyISAM 引擎的表会有一些限制,不过现在谁还用 MyISAM 呢?
3.5 GTID 模式配置实战
下面是我在生产环境中常用的配置。你直接复制到 my.cnf 里就行。
[mysqld]
# 必须开启 Binlog
log_bin = /var/log/mysql/mysql-bin.log
# 使用 ROW 格式
binlog_format = ROW
# 开启 GTID 模式
gtid_mode = ON
# 强制 GTID 一致性
enforce_gtid_consistency = ON
# 每个 Binlog 文件大小,默认 1G
max_binlog_size = 1G
# Binlog 保留天数,根据磁盘大小调整
expire_logs_days = 7
# 或者用 binlog_expire_logs_seconds(MySQL 8.0 推荐)
# binlog_expire_logs_seconds = 604800
# 记录 DDL 语句
binlog_rows_query_log_events = ON
配置完记得重启 MySQL。然后检查一下:
mysql> SHOW VARIABLES LIKE 'gtid_mode';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode | ON |
+---------------+-------+
mysql> SHOW VARIABLES LIKE 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
确认无误后,Debezium 的连接器配置里,把 database.history.store.only.monitored.tables.ddl 设为 true,这样它就能利用 GTID 做精确的断点续传了。
3.6 Binlog 清理策略:别让日志撑爆磁盘
Binlog 是持续增长的。如果不清理,磁盘迟早会满。我见过一个案例,某团队没配清理策略,结果 Binlog 把 500G 的磁盘写满了,MySQL 直接宕机。嗯,那场面,挺酸爽的。
清理策略主要有三种:
| 策略 | 配置方式 | 适用场景 | 注意事项 |
|---|---|---|---|
| 按时间清理 | expire_logs_days = 7 |
通用场景,最常用 | MySQL 8.0 推荐用 binlog_expire_logs_seconds |
| 按大小清理 | max_binlog_size = 1G |
控制单个文件大小 | 不会自动删除旧文件,需配合时间策略 |
| 手动清理 | PURGE BINARY LOGS |
紧急情况或维护窗口 | 小心别把 Debezium 正在读的 Binlog 删了 |
避坑指南:我曾经在清理 Binlog 时,没确认 Debezium 的消费位置,直接把 Binlog 删了。结果 Debezium 报错 Binlog file not found,只能重新做全量快照。那一次,10 亿条数据,跑了整整两天。所以,清理前一定要检查 Debezium 的消费进度。
检查 Debezium 消费位置的方法:
# 查看 Debezium 连接器的偏移量
curl -s http://localhost:8083/connectors/your-connector/offsets | jq .
或者直接查 Kafka 的 __consumer_offsets 主题。确保 Debezium 已经消费的 Binlog 位置早于你要清理的文件。
3.7 本章知识体系总览
下面这张图,帮你把 Binlog 的核心知识点串起来。建议你保存下来,以后排查问题时对照着看。
3.8 小结
Binlog 是 Debezium 的命根子。你只要记住三点:
- 格式选 ROW,别碰 STATEMENT 和 MIXED。
- GTID 必须开,这是 Debezium 高可用的基础。
- 清理策略要配好,别让 Binlog 撑爆磁盘,也别删了 Debezium 正在读的日志。
这些配置搞定了,Debezium 的底层基础就稳了。后面咱们再聊连接器配置和监控,你就不会因为 Binlog 的问题抓瞎了。