--- tags: [MySQL, Binary Log, binlog, WAL] create time: 2026-05-16 00:00 --- # Binary Log(Binlog) ## 概述 Binlog 是 MySQL Server 层生成的**逻辑日志**,记录所有修改数据的 SQL 语句(或行变更)。它是主从复制、时间点恢复和增量备份的基础。 ### 知识地图 ```mermaid flowchart LR A["Binary Log"] --> B["主从复制
从库通过 relay log 重放 binlog"] A --> C["时间点恢复 PITR
恢复到任意精确时刻"] A --> D["增量备份
基线备份 + binlog 增量"] A --> E["审计分析
追踪数据变更历史"] A --> F["崩溃一致性
与 Redo Log 2PC 协作"] style A fill:#6A0DAD,color:#fff ``` > [!TIP] 读这一页之前建议先了解 > 对 Binlog 的理解建立在 InnoDB 存储引擎基础之上。**建议先看**: [[hhs/MySQL/21-InnoDB 架构]] → 本文档 → [[hhs/MySQL/30-主从复制]] ## Binlog 与 Redo Log 的根本区别 ```mermaid graph LR subgraph Redo_Log["Redo Log"] R1["InnoDB Engine 专属"] R2["物理日志
记录页级修改"] R3["循环写入
固定大小"] R4["保证崩溃恢复"] end subgraph Binlog["Binlog"] B1["Server Layer 通用
所有引擎都写"] B2["逻辑日志
记录 SQL 或行变更"] B3["追加写入
可无限增长"] B4["保证主从一致 + PITR"] end style R1 fill:#C44569,color:#fff style B1 fill:#00B6BC,color:#fff ``` ### 关键对比 | 特性 | Redo Log | Binlog | |------|----------|--------| | **层级** | InnoDB 引擎层 | MySQL Server 层 | | **范围** | 只有 InnoDB 使用 | 所有引擎都可使用 | | **内容** | 物理:页偏移+字节修改 | 逻辑:SQL 或行变更 | | **写入方式** | 循环覆盖 | 追加 append-only | | **用途** | Crash Recovery | 主从复制、PITR、审计 | | **2PC 协调** | Phase 1: Redo prepare | Phase 2: Binlog fsync → redo commit | ## Binlog Format 三种模式 ### Statement 模式 每条修改数据的 SQL 作为一条 event 写入。主从复制时,从库直接执行这条 SQL: ``` # At 12345 #260516 10:30:00 server id 1 end_log_pos 12378 CRC32 0xabc123 UPDATE orders SET status = 'shipped' WHERE id = 100; ``` > [!QUESTION] Statement 模式简单直观,那为什么还要有其他模式? > > 关键问题:**NOW()、RAND()、UUID() 这些非确定性函数**。主库和从库的执行时间不同,结果就不同——这会导致主从数据不一致。例如:`UPDATE scores SET grade = NOW()` 在主库和执行时是 10:30:00,但从库可能在 10:30:01 执行,导致两个库的该记录值不同。 | 优点 | 缺点 | |------|------| | 体积小,binlog 少 | 某些函数(NOW(), RAND())结果在主从不一致 | | 无需额外存储空间 | INSERT ... SELECT 产生大量 binlog | | | 复杂触发器行为可能不同 | ### Row 模式 只记录行的实际变化(增删改前后的数据),不关心 SQL 是什么。从库拿到的是「数据快照」,直接重放即可: ``` ### @1=100 (BIGINT) ### @2='pending' (VARCHAR(20)) ### @3=50.00 (DECIMAL(10,2)) ### @4='2026-05-16 10:30:00' (DATETIME) ### UPDATE `app_db`.`orders` ### WHERE @1=100 ### @2='pending' ### @3=50.00 ### @4='2026-05-16 10:30:00' ### SET ### @1=100 ### @2='shipped' ### @3=50.00 ### @4='2026-05-16 10:30:00' ``` > [!TIP] 理解 Row 模式的关键 > 上面每行 `@n=值 (类型)` 表示第 n 个列的值和 MySQL 内部类型。WHERE 子句定位原行,SET 子句给出新值。虽然比 Statement 模式冗长很多,但**完全不受函数非确定性影响**——这就是为什么生产环境强烈推荐 ROW。 | 优点 | 缺点 | |------|------| | 主从数据绝对一致 | 体积极大(尤其是大批量 UPDATE)| | 支持 DDL 复制 | DELETE 全表扫描时极度膨胀 | | 不依赖函数确定性 | | | | BINLOG 格式无法直接阅读 | ### Mixed 模式 Statement 为主,遇到不确定情况切 Row: | 触发条件 | 切换为 Row | |---------|-----------| | 表没有主键 | ✅ | | 使用了 UUID() / RAND() | ✅ | | INSERT DELAYED | ✅ | | UPSERT (REPLACE/ON DUPLICATE KEY) | ✅ | ```ini binlog_format = STATEMENT # 传统默认(已废弃) binlog_format = ROW # 生产推荐 ✅ binlog_format = MIXED # 折中方案 ``` > [!WARNING] 强烈建议使用 ROW 模式 > Oracle 官方在 5.7+ 已将默认值改为 ROW。Statement 模式下「主库正确但从库错误」是常见的线上事故原因。 ### 总结:如何选择? > [!QUESTION] 如果生产环境必须三选一,你会选哪个? > > 答案:**ROW**。理由很简单——数据一致性永远排在体积和可读性之前。虽然 ROW 模式的 binlog 体积更大,但它消除了所有不确定性,而节省空间带来的好处远不及一次主从不一致造成的损失。 ## Binlog 文件结构 ``` /var/lib/mysql/ ├── mysql-bin.000001 ← 二进制日志文件 ├── mysql-bin.000002 ← 自动增长 ├── mysql-bin.000003 ├── mysql-bin.index ← 索引文件,列出所有 binlog └── binary.log ← 备用名称(某些配置) ``` ### Binlog 文件组织 ```mermaid graph TD A["mysql-bin.index"] -->|指向| B["mysql-bin.000001"] A -->|指向| C["mysql-bin.000002"] A -->|指向| D["mysql-bin.nnn"] B --> E["Format Description Event"] B --> F["Query Event"] B --> G["Rows / Xid Events"] C --> H["Format Description Event"] style A fill:#FF8C00,color:#fff ``` > [!QUESTION] 为什么需要 index 文件? > Binlog 是 append-only 的,新的事务不断写入。index 文件记录了所有有效的 binlog 文件列表,这样 MySQL 启动时可以快速定位到最新的 binlog 位置,而不用扫描整个磁盘。 ### 四种核心 Event | Event 类型 | 作用 | 示例 | |-----------|------|------| | **Query Event** | 执行 DDL 或非事务型 DML | CREATE TABLE, ALTER TABLE | | **Rows Event** | 记录行变更(ROW 模式)| INSERT_ROWS, UPDATE_ROWS, DELETE_ROWS | | **Xid Event** | 事务提交标记 | COMMIT #1001 | | **Format Description Event** | 描述 binlog 版本和格式 | 每个文件第一条 | ## Binlog 核心参数 ```ini [mysqld] server_id = 1 # 必须唯一,用于主从识别 log_bin = /var/log/mysql/mysql-bin # 开启并指定路径 binlog_format = ROW # 推荐 ROW binlog_row_image = FULL # 完整行(可选值:FULL / MINIMAL / NOBLOB) binlog_expire_logs_seconds = 604800 # 7 天自动清理 max_binlog_size = 100M # 单文件最大大小 sync_binlog = 1 # 每次 commit 都刷盘(安全) gtid_mode = ON # GTID 模式 enforce_gtid_consistency = ON # 强制 GTID 兼容的事务 binlog_checksum = CRC32 # 完整性校验 ``` ### binlog_row_image 参数详解 > [!TIP] 为什么默认是 FULL? > `FULL` 记录修改前后的所有列值,最简单也最安全。当某些列是 BLOB 类型且非常大时,可以改用 `MINIMAL`(只记录被修改的列 + 主键)来节省空间和性能。 | 值 | 行为 | 适用场景 | |----|------|---------| | **FULL** | 记录所有列的值 | 通用场景,默认值 ✅ | | **MINIMAL** | 仅记录被修改的列 + 必要索引列 | 大表 UPDATE,减少 binlog 体积 | | **NOBLOB** | 与 FULL 相同,但排除 BLOB/TEXT 列 | 包含大文本字段的表 | ### sync_binlog 与安全性 | 值 | 行为 | 性能损失 | 丢数据风险 | |----|------|---------|-----------| | **0** | OS 决定何时刷盘 | 最低 | 断电丢失整个缓冲区 | | **1** | 每次 commit 都刷盘 | 最高 | 零 ❌ | | **N** | 每 N 次 commit 刷一次 | 中等 | 最多丢 N 个事务 | > [!TIP] sync_binlog 与 innodb_flush_log_at_trx_commit > - 两者都设为 1 = 最强的 ACID 保证 > - 两者配合保证了即使服务器宕机也不会丢失任何已提交事务 > - 对 SSD 磁盘,sync_binlog=1 的性能损耗约 5%~10%,完全可接受 ### gtid_mode 的作用 > [!TIP] 为什么推荐使用 GTID? > 传统基于 Position 的主从复制在故障转移时需要手动计算 position,容易出错。GTID 为每个事务分配全局唯一 ID,复制时只需要知道"哪些事务已经执行过"即可,大大简化了主从切换和恢复流程。 ### binlog 清理策略 > [!WARNING] 千万不要直接删除 binlog 文件! > > 误删会导致 MySQL 无法识别索引与磁盘文件的对应关系,引发启动失败或数据不一致。 | 方式 | 命令 | 说明 | |------|------|------| | **按时间自动清理** | `binlog_expire_logs_seconds = 604800` | 7 天过期自动回收 ✅推荐 | | **手动清理** | `PURGE BINARY LOGS BEFORE '2026-05-09 00:00:00';` | 保留指定日期之前的 binlog | | **清理到指定文件** | `PURGE BINARY LOGS TO 'mysql-bin.000003';` | 清除 000003 之前的所有 binlog | | **清空所有(谨慎)** | `RESET MASTER;` | ⚠️ 仅用于测试环境 | ## 查看和解析 Binlog ### 服务端查询 ```sql -- 当前正在写的 binlog 文件(主从架构中查看主库状态) SHOW MASTER STATUS\G -- 列出所有 binlog 文件及大小 SHOW BINARY LOGS; -- 查看当前 binlog 位置(5.7+ 推荐用法) SHOW BINARY LOG STATUS\G ``` > [!TIP] `SHOW MASTER STATUS` vs `SHOW BINARY LOG STATUS` > MySQL 8.0.23+ 更推荐使用 `BINARY LOG STATUS`,因为从库也可以执行这个命令(MASTER 一词在复制语境下对从库不语义准确)。两者功能相同。 ### 命令行解析 ```bash # 命令行解析 binlog(-v 展开列名,DECODE-ROWS 以可读格式展示行变更) mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001 # 按时间范围解析(常用于精确时间点恢复) mysqlbinlog --start-datetime="2026-05-16 00:00:00" \ --stop-datetime="2026-05-16 12:00:00" \ mysql-bin.000001 > decoded.sql # 按 position 位置解析(精确到事务边界) mysqlbinlog --start-position=1234 --stop-position=5678 mysql-bin.000001 ``` > [!QUESTION] 如何定位一个错误操作的具体 position? > > 1. 用 `mysqlbinlog --start-datetime="..." --stop-datetime="..." file | grep "DELETE FROM"` 找到可疑 SQL > 2. 查看 SQL 上方的 `# at XXXXX` 确定起始 position > 3. 再结合 `--stop-position` 可以只回滚特定事务区间 ## 关联笔记 - [[hhs/MySQL/30-主从复制]] — Binlog 是主从复制的基石 - [[hhs/MySQL/23-ACID 与原子性实现]] — Binlog 与 Redo Log 的两阶段提交协议 - [[hhs/MySQL/32-备份与恢复]] — 基于 binlog 的 PITR 时间点恢复