Skip to content

Redo Log 与 Undo Log

Redo Log 和 Undo Log 是 InnoDB 事务机制的两大支柱——一个是"已做的事要记住",一个是"做过的事要能撤销"。


Redo Log:事务的"已做事日志"

为什么需要 Redo Log

MySQL 的数据最终存在磁盘上,但磁盘 I/O 是最慢的操作。

裸写数据页的问题:
1. UPDATE orders SET status = 'paid' WHERE id = 1;
2. 从磁盘读数据页到内存(16KB,1次 I/O)
3. 修改内存中的数据页
4. 刷盘(16KB,1次 I/O)
5. 事务提交完成

问题:如果事务提交后、刷盘前崩溃了,数据丢了!

Redo Log 解决了这个问题:把"修改了什么"先记到顺序写的日志文件,然后后台慢慢刷盘

Redo Log 的工作原理

事务提交时(innodb_flush_log_at_trx_commit=1):
┌────────────┐    ┌────────────┐    ┌────────────┐    ┌────────────┐
│ 修改内存    │ →  │ 写入RedoLog │ →  │ 刷盘RedoLog │ →  │ 后台刷数据页│
│ 数据页     │    │  (顺序写)   │    │  (保证持久)  │    │            │
└────────────┘    └────────────┘    └────────────┘    └────────────┘

Redo Log 记录的是"物理修改"

Redo Log 示例(简化):
Page {space=1, page_no=5} offset 128: 
  old_bytes=[41 61 6E]  // "Amy" 的 ASCII
  new_bytes=[41 6C 69]  // "Ali" 的 ASCII

Redo Log 的刷盘策略

sql
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
参数值行为持久性性能
0每秒刷盘一次,事务提交不刷盘最多丢1秒数据最快
1每次事务提交都刷盘(默认)绝不丢数据较慢
2每次事务提交刷到系统缓存,由系统决定何时刷盘机器宕机可能丢数据较快
各策略对比:
innodb_flush_log_at_trx_commit=0(最快):
事务提交 → 写入 Log Buffer → 每秒 fsync → 后台刷盘
风险:崩溃可能丢最近1秒的数据

innodb_flush_log_at_trx_commit=1(最安全):
事务提交 → 写入 Log Buffer → fsync → 数据保证持久
推荐用于:金融、订单、库存等强一致性场景

innodb_flush_log_at_trx_commit=2(折中):
事务提交 → 写入 Log Buffer → 刷到系统页缓存 → 后台 fsync
风险:机器断电时(操作系统崩溃),可能丢数据
推荐用于:可以容忍少量数据丢失的场景

Redo Log 的文件结构

ib_logfile0(默认 48MB)
ib_logfile1(默认 48MB)
ib_logfile2(可选)

┌────────────────────────────────────────┐
│ Redo Log 文件循环使用                    │
│                                        │
│  ┌────┬───────────────────────┬────┐  │
│  │写入│                       │检查点│  │
│  └────┴───────────────────────┴────┘  │
│  LSN  checkpoint                    LSN │
│  起点                                  │
│                                        │
│  文件写满后循环到开头(覆盖)             │
│  但只有已刷盘的日志才能被覆盖              │
└────────────────────────────────────────┘
参数默认值说明
innodb_log_file_size48MB每个日志文件大小
innodb_log_files_in_group2日志文件数量
innodb_log_buffer_size16MBLog Buffer 大小

Redo Log 在崩溃恢复中的作用

崩溃恢复流程:
1. MySQL 重启
2. 读取 Redo Log(从检查点开始)
3. 重做(REDO)所有已记录但未刷盘的事务
4. 读取 Undo Log
5. 回滚(UNDO)未提交的事务
6. 恢复正常服务

Redo Log 是"前滚"的:把已提交事务的修改重做,即使刷盘失败也能恢复。


Undo Log:事务的"已做事撤销"

为什么需要 Undo Log

Redo Log 记录的是"正向操作",用于恢复已提交事务。

但如果是未提交的事务呢?或者事务回滚呢?这就需要 Undo Log。

Undo Log 的两个作用:
1. 事务回滚:把修改前的值恢复回去
2. MVCC:提供历史版本供其他事务读取

Undo Log 记录的是什么

Undo Log 记录的是"反向操作"——也就是"怎么撤销"。

sql
-- 原始数据:id=1, name='Amy', balance=1000

-- UPDATE orders SET name='Tom', balance=balance-100 WHERE id=1;

Undo Log 记录:
{
    type: UPDATE,
    table: 'orders',
    where: id=1,
    before_image: { name: 'Amy', balance: 1000 },  -- 旧值
    after_image:  { name: 'Tom', balance: 900 }     -- 新值
}

回滚时:用 before_image 覆盖当前数据即可。

Undo Log 与版本链

Undo Log 是 MVCC 版本链的载体:

聚簇索引记录:
id=1, name='Tom', TRX_ID=200, ROLL_PTR ─────────────────┐

Undo Log(事务200的UPDATE):                      ┌─────────────────────┐
┌─────────────────────────┐                        │ id=1, name='Amy',   │
│ UPDATE: name 'Amy'→'Tom' │ ←─────────────┐       │ TRX_ID=200,         │
│ balance: 1000→900       │              │       │ ROLL_PTR ───────────┘
└─────────────────────────┘              │       │ name='Tom'          │
                                          │       └─────────────────────┘
         版本链读取方向 ←─────────────────┘
         
事务A(TRX_ID=150)的 ReadView:
  row.trx_id=200 在活跃列表?否(已提交)
  但沿着版本链走,找到旧版本 TRX_ID=100
  TRX_ID=100 < min_trx_id → 可见
  → 事务A 看到 name='Amy'

Undo Log 的物理存储

Undo Log 存储在系统表空间(ibdata)或独立的 Undo 表空间中。

InnoDB 会在 Rollback Segment 中维护 Undo Page:

┌─────────────────────────────────────────┐
│    Rollback Segment(回滚段)            │
│  ┌───────────────────────────────────┐ │
│  │  Undo Slot 1 → Undo Log 1         │ │
│  │  Undo Slot 2 → Undo Log 2         │ │
│  │  Undo Slot 3 → (空)               │ │
│  │  ...                               │ │
│  └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
参数说明
innodb_rollback_segments回滚段数量(默认 128)
innodb_undo_tablespaces独立 Undo 表空间数量
innodb_undo_log_truncate是否自动清理 Undo Log

Undo Log 的 purge

Undo Log 不能无限增长,purge 线程定期清理:

Undo Log 清理条件:
1. 没有活动事务需要这个 Undo Log 版本
2. 这个 Undo Log 产生的 ReadView 已经被"超过"
3. purge 线程将其标记为空闲

Redo + Undo 的协同

两阶段提交:Redo + Binlog

这是 MySQL 最经典也是最复杂的机制之一。

普通事务提交流程(无 Binlog):
1. 事务提交
2. 写 Redo Log(prepare 阶段)
3. 刷盘
4. 写 Undo Log
5. 提交完成

两阶段提交(有 Binlog):
┌────────────────────────────┐
│  1. InnoDB:Redo Log prepare     │
│     写入 Redo Log,状态=prepare   │
│     刷盘(fsync)                  │
├────────────────────────────┤
│  2. MySQL Server:写 Binlog      │
│     写入 Binlog                   │
│     刷盘(fsync)                  │
├────────────────────────────┤
│  3. InnoDB:Commit               │
│     Redo Log 状态改为 commit      │
│     事务提交完成                  │
└────────────────────────────┘

为什么需要两阶段提交?

问题:如果只写 Redo Log,不写 Binlog?

事务提交成功,Redo Log 已刷盘
崩溃恢复:根据 Redo Log 恢复数据
但主从复制时,从库没有这条数据!
主从不一致!

问题:如果只写 Binlog,不写 Redo Log?

Binlog 已刷盘,但数据页还没刷盘
崩溃恢复:从 Binlog 恢复?但 MySQL 无法从 Binlog 恢复用户数据
数据丢失!

两阶段提交保证了:Binlog 和 Redo Log 的数据一致性。

崩溃恢复时的判断:
┌────────────────────────────────────────────────┐
│ Redo Log 有 | Binlog 有 | 结果                   │
├────────────────────────────────────────────────┤
│ prepare | 有     | COMMIT(重做)              │
│ prepare | 没有   | ROLLBACK(撤销)             │
│ commit  | 有     | COMMIT(重做)              │
│ commit  | 没有   | 不可能发生                   │
└────────────────────────────────────────────────┘

性能优化:组提交

两阶段提交虽然保证了一致性,但性能较差。MySQL 通过组提交(Group Commit)优化:

sql
-- 多个事务的 Binlog 可以"打包"一次 fsync
-- 多个事务的 Redo Log prepare 可以合并

-- 查看组提交统计
SHOW STATUS LIKE 'innodb_log%';
-- Innodb_log_waits:因日志刷盘等待的次数
-- Innodb_os_log_pending_fsyncs:待刷盘的日志数

日志参数调优

sql
-- Redo Log 大小
SHOW VARIABLES LIKE 'innodb_log_file_size';
-- 推荐:单个文件 1GB,总大小 4GB~8GB

-- Log Buffer 大小
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
-- 默认 16MB,对于大事务可适当调大

-- 刷盘策略
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
-- 生产建议:1(金融场景)或 2(可容忍少量丢失)

-- Undo Log 表空间(MySQL 8.0)
SHOW VARIABLES LIKE 'innodb_undo%';
-- innodb_undo_tablespaces = 2(推荐)
-- innodb_undo_log_truncate = ON(自动清理)

小结

Redo Log vs Undo Log:

维度Redo LogUndo Log
记录什么修改后的值(新值)修改前的值(旧值)
记录格式物理(页+偏移)逻辑(SQL 逆操作)
作用崩溃恢复(重做)事务回滚 + MVCC
持久性必须持久化(事务提交时刷盘)不需要持久化
何时清理已刷盘的可被覆盖无事务需要时purge

两阶段提交:Redo Log prepare + Binlog + Commit,保证主从数据一致。


下一步

Binlog 是 MySQL 主从复制的核心日志。它记录了什么?怎么用它恢复数据或做增量同步?

Binlog 详解 继续。

基于 VitePress 构建