本页内容受版权保护 · 已添加水印 · 禁止任何形式转载
日志与恢复(redo / undo / binlog / 两阶段提交) · undo log 与回滚

undo log 与 redo log 的区别?两者都是用来做什么?

What is the difference between undo log and redo log? What are their respective purposes?

答案语言

考察要点

这道题考察的是候选人对 InnoDB 存储引擎核心机制的理解深度。面试官想看你是否真正掌握了数据库事务 ACID 特性中**原子性(Atomicity)和持久性(Durability)的底层实现原理。同时,这也是理解 MVCC(多版本并发控制) 和崩溃恢复(Crash Recovery)**机制的基础。

核心概念

redo log(重做日志)和 undo log(回滚日志)是 InnoDB 存储引擎为保证数据完整性和高并发性能而设计的两种核心日志。


1. Redo Log (重做日志)

  • 核心作用:保证事务的持久性(Durability)。
  • 工作原理:它遵循 WAL(Write-Ahead Logging) 原则,即在数据页(Data Page)被修改并写入磁盘之前,必须先将相应的修改记录写入 redo log 文件。如果数据库在将内存中的“脏页”刷回磁盘前宕机,重启后可以通过重放 redo log 来恢复这些已提交事务的修改,确保数据不会丢失。
  • 记录内容:物理日志(Physical Log)。记录的是对数据页的物理修改,例如“在表空间 X 的 Y 号数据页的 Z 偏移量处,写入数据'...'”。这种格式非常紧凑,恢复速度快。
  • 存储形式:存储在磁盘上专门的 redo log 文件中(如 ib_logfile0, ib_logfile1),大小固定,并以循环写入的方式工作。当写到文件末尾时,会回到开头覆盖旧的、已经同步到磁盘数据文件中的日志。
  • 生命周期:
    1. 事务执行 UPDATE/INSERT/DELETE 时,生成 redo log 记录并写入内存中的 redo log buffer。
    2. 事务 COMMIT 时,redo log buffer 的内容被刷入磁盘上的 redo log 文件(具体策略由 innodb_flush_log_at_trx_commit 控制)。
    3. 当 InnoDB 将内存中被修改的数据页(脏页)成功刷写到磁盘数据文件后,对应的 redo log 记录就完成了它的使命,在下一次循环写入时可以被覆盖。这个刷盘动作称为 Checkpoint。

2. Undo Log (回滚日志)

  • 核心作用:
    1. 保证事务的原子性(Atomicity):当事务需要回滚(ROLLBACK)时,InnoDB 可以利用 undo log 中的信息撤销之前的修改,将数据恢复到事务开始前的状态。
    2. 实现 MVCC(多版本并发控制):在读已提交(Read Committed)和可重复读(Repeatable Read)隔离级别下,当一个事务需要读取被另一个未提交事务所修改的行时,它会通过 undo log 读取该行之前的版本,从而实现非阻塞的“一致性读”。
  • 记录内容:逻辑日志(Logical Log)。记录的是与实际操作相反的逻辑操作。
    • 对于 INSERT,undo log 记录一个 DELETE 操作(包含主键信息)。
    • 对于 DELETE,undo log 记录一个 INSERT 操作(包含被删除行的所有列值)。
    • 对于 UPDATE,undo log 记录一个 UPDATE 操作,将列更新为旧值。
  • 存储形式:存储在 InnoDB 的表空间内,可以是系统表空间(ibdata1),也可以是独立的 undo 表空间(MySQL 5.6+ 推荐配置)。它被组织成一个版本链,每一行数据的隐藏列 DB_ROLL_PTR 指向其在 undo log 中的上一个版本。
  • 生命周期:
    1. 事务修改数据时,生成对应的 undo log 记录。
    2. 事务提交后,undo log 不会立即被删除。它需要保留,直到系统中不再有任何事务需要通过它来获取旧版本数据(即满足 MVCC 的可见性规则)。
    3. 后台的 Purge 线程会定期检查并清理那些不再需要的 undo log 记录。

总结对比

特性Redo Log (重做日志)Undo Log (回滚日志)
核心目的持久性 (Durability)、崩溃恢复原子性 (Atomicity)、MVCC
记录内容物理修改(Page ID, Offset, new value)逻辑逆操作(旧行数据、反向 SQL)
写入方式顺序追加写入,循环使用离散写入,作为数据版本链
存储位置专用的 redo log 文件(如 ib_logfile*)系统表空间或独立的 undo 表空间
释放时机对应的数据脏页刷盘后(Checkpoint)当不再有任何事务需要访问该旧版本时
作用阶段崩溃恢复时向前滚(Redo)事务回滚时向后滚(Undo),MVCC 读旧版

常见低分回答

  1. 混淆二者作用:错误地回答“redo log 用于回滚,undo log 用于持久化”。这是最致命的错误,直接暴露了对基础概念的混淆。
  2. 忽略 MVCC:只提到 undo log 用于事务回滚,完全不提其在 MVCC 中为实现一致性读提供数据旧版本的重要作用。这表明理解不够深入,因为 MVCC 是 InnoDB 并发控制的核心。
  3. 与 Binlog 混淆:将 redo log 的作用与 binlog 混淆,例如说“redo log 用于主从复制”。虽然它们在事务提交时通过两阶段提交(2PC)协同工作,但 binlog 是 Server 层的归档日志,用于复制和时间点恢复,而 redo log 是 InnoDB 引擎层的物理日志,用于崩溃恢复。
  4. 对存储和生命周期描述不清:回答“它们都是写日志文件”,但说不出 redo log 的循环写特性和 undo log 作为数据一部分被 Purge 线程回收的区别。这显示出对底层实现细节缺乏了解。
  5. 描述过于笼统:“它们都是保证 ACID 的日志”。这种回答过于宽泛,没有体现出二者在 ACID 中分别扮演的具体角色(D vs A)以及它们如何协同工作。

工程实践与优化

  • Redo Log 调优:
    • 日志文件大小 (innodb_log_file_size * innodb_log_files_in_group):这个总大小决定了 InnoDB 的写入性能和恢复时间。太小会导致频繁的 Checkpoint,增加 I/O 负担;太大则会延长崩溃恢复的时间。通常建议设置为能容纳系统 1-2 小时高峰期写入量的日志。
    • 提交刷盘策略 (innodb_flush_log_at_trx_commit):
      • 1(默认):每次事务提交都将 redo log 从 buffer 同步刷到磁盘。最安全,保证了严格的持久性,但性能最低。
      • 2:每次提交只写入操作系统的 Page Cache,每秒再由后台线程刷盘。性能较高,但若操作系统宕机,可能丢失最后一秒的事务。
      • 0:每秒才将 redo log buffer 刷到 Page Cache 并刷盘。性能最高,但若 MySQL 实例宕机,可能丢失最后一秒的事务。
  • Undo Log 调优:
    • 监控与防止膨胀:长事务是 undo log 空间膨胀的罪魁祸首,因为它会阻止 Purge 线程回收旧版本。应通过 information_schema.innodb_trx 监控长事务并及时处理。
    • 独立 Undo 表空间:从 MySQL 5.7 开始,强烈建议启用独立的 undo 表空间 (innodb_undo_tablespaces > 1)。这使得在 undo 表空间过度膨胀后,可以通过在线收缩(MySQL 8.0.18+)或离线重建的方式回收磁盘空间,而系统表空间 ibdata1 一旦增长则无法收缩。
    • Purge 线程:对于高并发写入的系统,可以适当增加 innodb_purge_threads 的数量,以加快 undo log 的回收速度,防止积压。

常见追问与延伸题目

  1. 追问:事务提交时,redo log 和 binlog 是如何协同工作的?为什么需要两阶段提交?

    • 回答要点:为了保证 InnoDB 存储引擎(依赖 redo log)和 Server 层(依赖 binlog)之间的数据一致性,特别是在主从复制或基于 binlog 的恢复场景下,MySQL 采用两阶段提交(2PC)。
      1. Prepare 阶段:InnoDB 将事务的 redo log 写入磁盘,并将事务状态置为 prepare。
      2. Commit 阶段:Server 层将该事务的 binlog 写入磁盘,然后通知 InnoDB 将事务状态置为 commit,InnoDB 写入一个 commit 标记到 redo log 中。
    • 必要性:这个机制防止了在主库崩溃时,出现“从库比主库多数据”(先写 binlog 后写 redo log)或“从库比主库少数据”(先写 redo log 后写 binlog)的不一致情况。两阶段提交保证了 redo log 和 binlog 的写入是一个原子操作。
  2. 追问:如果 MySQL 实例突然断电,重启后会发生什么来恢复数据?

    • 回答要点:这是典型的崩溃恢复(Crash Recovery)过程。
      1. Redo 阶段:InnoDB 启动后,会检查磁盘上的 redo log。它会从上一个 Checkpoint 点开始,向后扫描 redo log,将所有已提交事务(有 commit 标记)但尚未写入数据文件的修改,重新应用(Replay)到内存的数据页中。这个过程保证了持久性。
      2. Undo 阶段:在完成 Redo 之后,InnoDB 会扫描 undo log,找出所有处于 active 状态(即在 redo log 中只有 prepare 记录,没有 commit 记录)的事务,并对这些未完成的事务执行回滚操作。这个过程保证了原子性。
  3. 追问:Undo log 是如何支持 MVCC 的?请描述一下过程。

    • 回答要点:
      1. 版本链:InnoDB 的每一行数据除了用户定义的列外,还有两个隐藏列:DB_TRX_ID(最后修改该行的事务 ID)和 DB_ROLL_PTR(指向该行上一个版本在 undo log 中的指针)。这些指针将一行数据的多个历史版本串联成一个版本链。
      2. Read View:当一个事务开始时(在 RR 隔离级别下)或执行每条 SELECT 语句时(在 RC 隔离级别下),会创建一个名为 Read View 的数据结构。它记录了当前活跃的事务 ID 列表,并定义了哪些版本的行数据对当前事务是可见的。
      3. 可见性判断:当事务读取某一行时,它会比较该行最新的 DB_TRX_ID 和自己的 Read View。
        • 如果 DB_TRX_ID 对当前事务可见,则直接读取该版本数据。
        • 如果不可见(例如,该行被一个在 Read View 创建后才开始的事务修改了),则通过 DB_ROLL_PTR 顺着版本链往下找,直到找到第一个 DB_TRX_ID 对当前事务可见的版本。这就是一致性读的实现。