跳到主要内容

日志系统

问题

MySQL 有哪些重要的日志?redo log、undo log 和 binlog 分别有什么作用?它们之间如何协同工作?什么是两阶段提交?

答案

MySQL 日志全景

日志层级作用类型
redo logInnoDB 引擎层崩溃恢复(crash-safe)物理日志
undo logInnoDB 引擎层事务回滚 + MVCC逻辑日志
binlogServer 层主从复制、数据恢复逻辑日志
慢查询日志Server 层记录慢 SQL文本日志
错误日志Server 层记录错误信息文本日志
通用查询日志Server 层记录所有 SQL文本日志

面试重点是前三个:redo log、undo log、binlog

Redo Log(重做日志)

作用

redo log 保证持久性(Durability)。它记录的是数据页的物理修改("在某个数据页的某个偏移量处写入了什么值"),用于崩溃恢复。

为什么需要 Redo Log

修改数据时如果每次都直接写磁盘(随机 I/O),性能极差。InnoDB 采用 WAL(Write-Ahead Logging) 策略:

WAL 的核心思想:先写日志(顺序 I/O,速度快),再写磁盘(随机 I/O,后台异步)。即使崩溃,也能通过 redo log 恢复未刷盘的数据。

Redo Log 的结构

redo log 采用固定大小,循环写入的方式:

  • write pos:当前写入位置,边写边后移
  • checkpoint:已刷盘到数据文件的位置,边擦边后移
  • write pos 和 checkpoint 之间的空间是可用空间
  • 当 write pos 追上 checkpoint 时,必须停下来先刷脏页

刷盘策略

innodb_flush_log_at_trx_commit 控制:

行为安全性性能
0每秒将 Log Buffer 写入 OS Cache 并 fsync最低(崩溃丢 1 秒)最高
1(默认)每次事务提交都 fsync 到磁盘最高(不丢数据)最低
2每次提交写入 OS Cache,每秒 fsync中等(OS 崩溃丢 1 秒)中等
生产建议

核心业务表设置 innodb_flush_log_at_trx_commit = 1,非核心表可设为 2 提升性能。

Undo Log(回滚日志)

作用

undo log 保证原子性(Atomicity) 和支持 MVCC

  1. 事务回滚:记录数据修改前的值,回滚时执行"反操作"还原数据
  2. MVCC 版本链:通过 DB_ROLL_PTR 指针串成版本链,实现快照读

Undo Log 的类型

类型产生时机说明
insert undo logINSERT 操作事务提交后即可删除(其他事务不需要读取新插入的旧版本)
update undo logUPDATE/DELETE 操作需要保留到没有事务需要读取时才能清理(MVCC 需要)

关于 MVCC 如何利用 undo log 版本链,参见 MVCC 多版本并发控制

Binlog(归档日志)

作用

binlog 是 MySQL Server 层的日志,所有存储引擎共用。核心作用:

  1. 主从复制:从库通过读取主库的 binlog 来同步数据
  2. 数据恢复:通过 binlog 可将数据库恢复到任意时间点

Binlog 的三种格式

格式记录内容优点缺点
StatementSQL 语句原文日志量小某些函数(如 NOW()UUID())可能导致主从不一致
Row行数据变更(修改前后的值)数据一致性好日志量大(尤其批量操作)
Mixed自动选择 Statement 或 Row折中方案仍可能不一致
推荐 Row 格式

MySQL 5.7.7+ 默认使用 Row 格式。虽然日志量较大,但数据一致性最好,且可以用于数据恢复(能精确看到哪一行被改成了什么值)。

刷盘策略

sync_binlog 控制:

行为
0由操作系统决定何时 fsync
1(推荐)每次事务提交都 fsync
N每 N 个事务 fsync 一次

Redo Log vs Binlog

对比维度Redo LogBinlog
层级InnoDB 引擎层MySQL Server 层
内容物理日志(数据页修改)逻辑日志(SQL 或行变更)
写入方式循环写,固定大小追加写,不覆盖
作用崩溃恢复(crash-safe)主从复制、数据归档恢复
事务支持有(InnoDB 独有)有(所有引擎)

两阶段提交

为了保证 redo log 和 binlog 的数据一致性,MySQL 使用两阶段提交(2PC)

为什么需要两阶段提交

假设不用两阶段提交:

场景 1:先写 redo log,后写 binlog

  • redo log 写完后崩溃,binlog 没写
  • 主库通过 redo log 恢复了数据,但从库没有这条 binlog → 主从不一致

场景 2:先写 binlog,后写 redo log

  • binlog 写完后崩溃,redo log 没写
  • 主库没有恢复数据(缺少 redo log),但从库执行了 binlog → 主从不一致

两阶段提交保证了:无论在哪一步崩溃,恢复后 redo log 和 binlog 都是一致的。

崩溃恢复规则

崩溃时机redo log 状态binlog 状态恢复操作
写 redo log (prepare) 之后prepare回滚事务
写 binlog 之后prepare提交事务(redo log 补 commit)
两阶段完成后commit正常,无需恢复
MySQL 8.0 优化

MySQL 8.0.17+ 引入了 binlog 组提交redo log 组提交,将多个事务的日志合并写入,减少 fsync 次数,大幅提升高并发下的性能。

一条 UPDATE 的完整日志流程

UPDATE user SET name = '李四' WHERE id = 1;

常见面试问题

Q1: redo log 和 binlog 有什么区别?

答案

三个核心区别:

  1. 层级不同:redo log 是 InnoDB 引擎层的,binlog 是 MySQL Server 层的(所有引擎共用)
  2. 内容不同:redo log 是物理日志(记录数据页修改),binlog 是逻辑日志(记录 SQL 或行变更)
  3. 用途不同:redo log 用于崩溃恢复(保证 crash-safe),binlog 用于主从复制和数据归档恢复

它们需要通过两阶段提交来保证一致性。

Q2: 什么是 WAL?为什么要用 WAL?

答案

WAL(Write-Ahead Logging) 即"日志先行":修改数据时,先将修改写入日志(redo log),再由后台线程异步将脏页写入磁盘。

为什么用 WAL:

  • 写日志是顺序 I/O,速度极快(磁盘顺序写接近内存速度)
  • 写数据页是随机 I/O,速度慢(磁头需要频繁寻道)
  • WAL 将随机写转化为顺序写,大幅提升写入性能
  • 即使崩溃,redo log 中也保存了修改信息,可用于恢复

Q3: 为什么需要两阶段提交?

答案

两阶段提交保证了 redo log 和 binlog 的一致性。如果两者不一致:

  • redo log 有但 binlog 没有:主库恢复了数据,从库缺少该变更 → 主从不一致
  • binlog 有但 redo log 没有:从库执行了变更,主库没恢复 → 主从不一致

两阶段提交的流程:

  1. Prepare 阶段:redo log 写入并标记为 prepare
  2. Commit 阶段:binlog 写入并 fsync,然后 redo log 标记为 commit

崩溃恢复时:如果 redo log 是 prepare 且 binlog 完整 → 提交事务;如果 binlog 不完整 → 回滚事务。

Q4: undo log 的两个作用分别是什么?

答案

  1. 事务回滚:undo log 记录了数据修改前的旧值。当事务执行 ROLLBACK 时,通过 undo log 将数据恢复到修改前的状态,保证原子性
  2. MVCC 版本链:undo log 通过 DB_ROLL_PTR 指针串成版本链。快照读时,InnoDB 沿着版本链找到对当前事务可见的旧版本,实现隔离性

undo log 的清理:由 Purge 线程负责,当没有活跃事务需要读取某个旧版本时才清理。长事务会导致 undo log 膨胀。

Q5: binlog 的三种格式怎么选?

答案

格式推荐程度说明
Row(推荐)⭐⭐⭐精确记录每行变更,数据一致性最好,支持精确的数据恢复和审计
Statement日志量小,但 NOW()UUID()RAND() 等函数可能导致主从不一致
Mixed⭐⭐自动判断用 Statement 还是 Row,折中方案

MySQL 5.7.7+ 默认使用 Row 格式,生产环境建议使用 Row 格式,配合 sync_binlog = 1 保证数据安全。虽然日志量较大,但存储成本远低于数据不一致的风险。

相关链接