跳到主要内容

日志系统

问题

MySQL 的 redo log、undo log、binlog 分别是什么?WAL 机制是什么?两阶段提交如何保证数据一致性?

答案

一、三大日志概览

日志所属层作用写入时机
redo logInnoDB 引擎层保证 持久性(崩溃恢复)事务执行中,先写 Buffer
undo logInnoDB 引擎层保证 原子性(回滚)+ MVCC修改前写入
binlogServer 层归档日志,主从复制、备份事务提交时写入

二、redo log(重做日志)

为什么需要 redo log?

InnoDB 修改数据时先修改 Buffer Pool 中的内存页(脏页),如果每次修改都立即写磁盘,随机 IO 开销巨大。

WAL(Write-Ahead Logging)

先写日志,再写磁盘。修改数据前先将变更写入 redo log(顺序写,速度快),然后修改内存。即使宕机,也能通过 redo log 恢复数据。

redo log 的写入流程

更新操作 → 写 redo log buffer → (flush) → 写 OS page cache → (fsync) → 写磁盘文件

刷盘策略innodb_flush_log_at_trx_commit):

行为性能安全性
0每秒批量写入和 fsync最高可能丢 1 秒数据
1每次提交都 fsync最低不丢数据
2每次提交写 page cache,每秒 fsync中间OS 崩溃可能丢 1 秒
生产推荐

innodb_flush_log_at_trx_commit = 1 是唯一能保证不丢数据的配置。只有在能容忍丢数据的场景(如导入数据)才临时设为 0 或 2。

redo log 的循环写入

redo log 是固定大小的循环文件组(默认两个文件,每个 48MB):

           write pos →
┌──────────────────────────────┐
│ ib_logfile0 │ ib_logfile1 │
└──────────────────────────────┘
← checkpoint
  • write pos:当前写入位置,向前推进
  • checkpoint:已刷到磁盘的位置
  • write pos 追上 checkpoint 时,必须先推进 checkpoint(将脏页刷盘)

三、undo log(回滚日志)

两大用途

  1. 事务回滚:记录修改前的数据(逻辑日志),回滚时执行反向操作
  2. MVCC 版本链:多个版本通过 undo log 串成版本链,支持快照读

不同操作的 undo log

操作undo log 内容类型
INSERT记录主键,回滚时 DELETEinsert undo log
DELETE记录整行数据,回滚时 INSERTupdate undo log
UPDATE记录旧值,回滚时恢复update undo log
  • insert undo log:事务提交后可立即删除(新插入的行对其他事务不可见)
  • update undo log:需要保留到没有活跃 Read View 引用时才能被 purge 线程回收

四、binlog(归档日志)

binlog vs redo log

对比项redo logbinlog
所属层InnoDB 引擎Server 层
日志类型物理日志(页级修改)逻辑日志(SQL 语句)
写入方式循环写,空间固定追加写,文件切换
用途崩溃恢复主从复制、数据备份
所有引擎仅 InnoDB所有引擎都有

binlog 的三种格式

格式记录内容优点缺点
STATEMENTSQL 语句本身日志量小不确定函数(NOW()、UUID())主从不一致
ROW每行数据的变更精确,不会不一致日志量大
MIXED自动选择折中仍有边界问题
生产推荐

MySQL 8.0 默认 ROW 格式,主从一致性最好,是推荐选择。

binlog 的写入流程

事务中 → 写 binlog cache(线程内存) → 事务提交 → write 到 page cache → fsync 到磁盘

sync_binlog 参数:

行为
0不主动 fsync,由 OS 决定
1每次提交都 fsync(最安全)
N每 N 次提交 fsync

五、两阶段提交

为什么需要两阶段提交?

redo log 和 binlog 是两个独立的日志系统。如果写入不是原子的:

  • 先写 redo log 后写 binlog:宕机时 redo log 恢复了数据,但 binlog 缺少这条记录,从库数据丢失
  • 先写 binlog 后写 redo log:宕机时 binlog 有记录但 redo log 没有,主库数据丢失,从库多了数据

两阶段提交流程

崩溃恢复规则

崩溃时间点redo log 状态binlog 状态恢复策略
prepare 写完,binlog 未写prepare回滚事务
binlog 写完,commit 未标记prepare完整提交事务
commit 写完commit完整提交事务

关键判断:如果 redo log 是 prepare 状态,检查 binlog 是否完整(通过 XID 匹配),完整则提交,否则回滚。

六、一条 UPDATE 的完整日志流程

UPDATE users SET name = '李四' WHERE id = 1;
1. 执行器找 InnoDB 读取 id=1 的行
2. InnoDB 从 Buffer Pool 或磁盘读取数据页
3. 写 undo log(记录旧值 name='张三')
4. 更新 Buffer Pool 中的数据页为 name='李四'
5. 写 redo log(prepare 状态)
6. 写 binlog
7. redo log 标记为 commit
8. 返回成功
组提交(Group Commit)

MySQL 5.7+ 支持 binlog 组提交,将多个事务的 fsync 合并为一次,大幅提升高并发场景的写入性能。


常见面试问题

Q1: 为什么 binlog 不能替代 redo log 做崩溃恢复?

答案

  1. binlog 是逻辑日志,没有记录数据页的物理状态,无法恢复内存中的脏页
  2. binlog 是追加写,不知道哪些数据页是脏的
  3. binlog 是 Server 层的,不了解 InnoDB 的 Buffer Pool 状态
  4. redo log 是物理日志,精确到页级修改,可以直接应用到数据页

Q2: redo log 满了怎么办?

答案
redo log 是循环写的。当 write pos 追上 checkpoint 时:

  1. 停止接收新的更新操作
  2. 将 checkpoint 向前推进(将脏页刷到磁盘)
  3. 刷完后释放空间,继续写入

这就是为什么 redo log 文件不能设太小——太小会频繁触发刷盘,影响性能。推荐配置:innodb_log_file_size = 1G(或根据写入量调整)。

Q3: 如何通过 binlog 恢复数据?

答案

# 1. 找到误操作前的 binlog 位置
mysqlbinlog --start-datetime="2024-01-01 10:00:00" \
--stop-datetime="2024-01-01 10:05:00" \
mysql-bin.000001

# 2. 通过 position 恢复
mysqlbinlog --start-position=154 --stop-position=1024 \
mysql-bin.000001 | mysql -u root -p

配合全量备份 + binlog 可以实现任意时间点恢复(Point-in-Time Recovery)。

Q4: innodb_flush_log_at_trx_commit 和 sync_binlog 怎么配?

答案

  • 双 1 配置(最安全):innodb_flush_log_at_trx_commit=1, sync_binlog=1
  • 适用于对数据安全要求高的业务(金融、交易等)
  • 代价是 IO 性能较低
  • 非关键业务可考虑 2, 11, 100 的折中方案

相关链接