11 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-05-16 00:00 |
Binary Log(Binlog)
概述
Binlog 是 MySQL Server 层生成的逻辑日志,记录所有修改数据的 SQL 语句(或行变更)。它是主从复制、时间点恢复和增量备份的基础。
知识地图
flowchart LR
A["Binary Log"] --> B["主从复制<br/>从库通过 relay log 重放 binlog"]
A --> C["时间点恢复 PITR<br/>恢复到任意精确时刻"]
A --> D["增量备份<br/>基线备份 + binlog 增量"]
A --> E["审计分析<br/>追踪数据变更历史"]
A --> F["崩溃一致性<br/>与 Redo Log 2PC 协作"]
style A fill:#6A0DAD,color:#fff
[!TIP] 读这一页之前建议先了解 对 Binlog 的理解建立在 InnoDB 存储引擎基础之上。建议先看: hhs/MySQL/04-存储引擎/19-InnoDB 深度解析 → 本文档 → hhs/MySQL/07-高可用与分布式/30-主从复制详解
Binlog 与 Redo Log 的根本区别
graph LR
subgraph Redo_Log["Redo Log"]
R1["InnoDB Engine 专属"]
R2["物理日志<br/>记录页级修改"]
R3["循环写入<br/>固定大小"]
R4["保证崩溃恢复"]
end
subgraph Binlog["Binlog"]
B1["Server Layer 通用<br/>所有引擎都写"]
B2["逻辑日志<br/>记录 SQL 或行变更"]
B3["追加写入<br/>可无限增长"]
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) | ✅ |
binlog_format = STATEMENT # 传统默认(已废弃)
binlog_format = ROW # 生产推荐 ✅
binlog_format = MIXED # 折中方案
[!WARNING] 强烈建议使用 ROW 模式 Oracle 官方在 5.7+ 已将默认值改为 ROW。Statement 模式下「主库正确但从库错误」是常见的线上事故原因。
总结:如何选择?
[!QUESTION] 如果生产环境必须三选一,你会选哪个?
答案:ROW。理由很简单——数据一致性永远排在体积和可读性之前。虽然 ROW 模式的 binlog 体积更大,但它消除了所有不确定性,而节省空间带来的好处远不及一次主从不一致造成的损失。
Row vs Statement 对特殊场景的影响
除了非确定性函数(NOW()、RAND())之外,还有两类场景在 STATEMENT 模式下容易引发主从不一致:
| 场景 | 问题原因 |
|---|---|
| 字符集 / Collation 不一致 | 主库和从库 collation 不同时,STATEMENT 模式下的字符串比较可能得出不同结果;ROW 模式传输原始字节,不受影响 |
| 自增 ID(auto_increment) | 主从并发写入时,STATEMENT 模式下从库的自增值可能与主库不同步,导致重复或跳跃 |
| INSERT_DELAYED | 5.6+ 已移除,执行时机不确定 |
结论:生产环境一律使用 binlog_format = ROW,避免上述所有隐患。
Binlog 文件结构
/var/lib/mysql/
├── mysql-bin.000001 ← 二进制日志文件
├── mysql-bin.000002 ← 自动增长
├── mysql-bin.000003
├── mysql-bin.index ← 索引文件,列出所有 binlog
└── binary.log ← 备用名称(某些配置)
Binlog 文件组织
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 核心参数
[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
服务端查询
-- 当前正在写的 binlog 文件(主从架构中查看主库状态)
SHOW MASTER STATUS\G
-- 列出所有 binlog 文件及大小
SHOW BINARY LOGS;
-- 查看当前 binlog 位置(5.7+ 推荐用法)
SHOW BINARY LOG STATUS\G
[!TIP]
SHOW MASTER STATUSvsSHOW BINARY LOG STATUSMySQL 8.0.23+ 更推荐使用BINARY LOG STATUS,因为从库也可以执行这个命令(MASTER 一词在复制语境下对从库不语义准确)。两者功能相同。
命令行解析
# 命令行解析 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?
- 用
mysqlbinlog --start-datetime="..." --stop-datetime="..." file | grep "DELETE FROM"找到可疑 SQL- 查看 SQL 上方的
# at XXXXX确定起始 position- 再结合
--stop-position可以只回滚特定事务区间
关联笔记
- hhs/MySQL/07-高可用与分布式/30-主从复制详解 — Binlog 是主从复制的基石
- hhs/MySQL/06-事务与并发控制/23-ACID 与原子性实现 — Binlog 与 Redo Log 的两阶段提交协议
- hhs/MySQL/07-高可用与分布式/34-备份与恢复 — 基于 binlog 的 PITR 时间点恢复