MySQL 核心日志:redo log、undo log 与 MVCC 深度讲解

MySQL 核心日志:redo log、undo log 与 binlog + MVCC 深度讲解(2026 年最新视角)

这三兄弟 + binlog 是 MySQL 能做到 crash-safe、可重复读、以及主从复制的绝对核心。我们今天一次性讲透它们底层实现细节、WAL 机制、与 InnoDB 缓冲池的关系、MVCC 精确工作原理。

一、redo log(重做日志)—— 实现 Crash-Safe 的物理日志

核心作用:保证已经提交的事务,即使宕机了,数据也一定能恢复(持久性 Durability)

  1. 属于 InnoDB 存储引擎层,不是 MySQL Server 层
  2. 是物理日志,记录的是“在某个数据页上做了什么修改”,例如:
  • 对页号 5 的偏移量 100 处改成了“姑苏慕容复”
  1. 采用 WAL(Write-Ahead Logging)技术
    先写日志,再写磁盘。随机写 → 顺序写,性能爆炸提升。

redo log 固定大小,循环写(循环利用)

默认由两个文件组成:ib_logfile0、ib_logfile1(可通过 innodb_log_files_in_group 改数量)

每个文件默认 48MB(MySQL 5.6),MySQL 8.0 默认 128MB/个,可配到 512GB

总大小由 innodb_log_file_size(已废弃)或 innodb_redo_log_capacity 控制(8.0.30+ 推荐)

关键参数(2026 年推荐配置)

innodb_redo_log_capacity = 4G      # 总 redo log 大小,建议 4~32G
innodb_flush_log_at_trx_commit = 1 # 最安全:每次 commit 都刷盘(生产推荐)
                               = 0 # 每秒刷一次,性能最好,宕机丢1秒数据
                               = 2 # commit 只写 os cache,每秒 fsync(性价比最高,推荐)

redo log 刷盘时机(重要!)

  • innodb_flush_log_at_trx_commit 控制
  • redo log 写到 3/4 满了会触发 checkpoint
  • background thread 每秒刷一次
  • 正常 shutdown 时全部刷盘

redo log 的两条核心刷盘规则(面试必问):

  1. 当 redo log 持久化到磁盘后,脏页才可以被淘汰出 buffer pool
  2. checkpoint 推进时,会把 checkpoint_lsn 之前的脏页刷到磁盘

二、undo log(回滚日志)—— 实现原子性 + MVCC 的基石

两大作用

  1. 事务回滚(Rollback)
  2. 多版本并发控制(MVCC)

undo log 是逻辑日志,记录的是“如何撤销上一次操作”的反向操作。

例如:

  • insert → undo 里记 delete
  • delete → undo 里记 insert
  • update → 旧版本记录下来(两种方式:更新前镜像 或 更新后只记改了哪些列)

undo log 存储位置变化(非常重要!)

  • MySQL 5.6 前:放在 ibdata1 共享表空间
  • MySQL 5.7+:默认放在单独的 undo tablespace(默认 2 个 undo001、undo002)
  • MySQL 8.0+:默认开启独立 undo tablespace,且支持 truncate(收缩)

关键参数

innodb_undo_tablespaces = 2     # 建议至少 2~8 个,循环使用,可 truncate
innodb_undo_log_truncate = on   # 8.0.14+ 开启自动收缩
innodb_max_undo_log_size = 4G   # 超过这个值会触发 truncate

回滚段(Rollback Segment)

InnoDB 共有 128 个回滚段(系统表空间固定 32 个 + undo tablespace 每个最多 96 个)

每个事务默认分配一个 rollback segment,可支持 128 个并发写事务(读不占)

三、MVCC(Multi-Version Concurrency Control)多版本并发控制 —— 实现可重复读的核心

MySQL 默认隔离级别是 REPEATABLE READ(RR),不是 READ COMMITTED!
正是靠 undo log + 隐藏列 + ReadView 实现的真正 RR(大多数人以为是间隙锁,其实不是)

每行记录的隐藏列(系统列)

  1. DB_TRX_ID(6字节):最近修改该行的事务 ID
  2. DB_ROLL_PTR(7字节):回滚指针,指向 undo log 中的旧版本
  3. DB_ROW_ID(6字节):隐藏主键(当表没有主键时)

ReadView(读视图)是 MVCC 最核心结构(面试必考)

每次事务开始快照读(普通 select)时生成一个 ReadView,包含:

  • m_ids:当前系统中活跃(未提交)的事务 ID 列表
  • min_trx_id:m_ids 中最小的 trx_id
  • max_trx_id:下一个将要分配的事务 ID
  • creator_trx_id:本事务自己的 trx_id

版本链可见性判断规则(经典三条)

对于当前行版本的 trx_id,我们判断:

  1. 如果 trx_id == creator_trx_id → 自己改的,肯定可见
  2. 如果 trx_id < min_trx_id → 已经提交的旧版本,肯定可见
  3. 如果 trx_id >= max_trx_id → 未来事务,肯定不可见
  4. 如果 min_trx_id ≤ trx_id < max_trx_id → 去 m_ids 中查找:
  • 在里面 → 活跃事务,不可見
  • 不在 → 已提交,可见

RC vs RR 隔离级别的最大区别(99%的人答错)

  • READ COMMITTED:每次普通 select 都生成新的 ReadView → 看到最新已提交版本(可出现不可重复读)
  • REPEATABLE READ:事务第一次 select 时生成 ReadView,后续全部复用 → 始终看到事务开始时的快照版本

当前读 vs 快照读

  • 快照读(普通 select):走 MVCC,不加锁,超高并发
  • 当前读(select … for update / update / delete):读取最新版本 + 加 X 锁 / 间隙锁

四、binlog(归档日志)—— 实现主从复制的逻辑日志

注意:binlog 属于 Server 层,所有引擎都会记(但只有 InnoDB 支持 crash-safe)

三种格式对比(2026 年强烈推荐 ROW)

格式优点缺点适用场景
STATEMENT日志量小主从数据不一致风险(now()/rand()/uuid())基本淘汰
ROW绝对安全,精确到行级变更日志量大 5~10 倍生产首选(2026 年标配)
MIXED自动选择(普通用 S,有风险用 R)仍然有少量不一致风险过渡期使用,已基本不用

推荐配置(2026 年生产标配)

binlog_format = ROW
binlog_row_image = MINIMAL      # 只记录改动的列,节省空间(8.0 推荐)
sync_binlog = 1                 # 最安全,每次 commit 都 fsync
innodb_flush_log_at_trx_commit = 1
transaction_isolation = REPEATABLE-READ

五、一次更新事务的完整执行流程(经典面试八股)

update user set name = '乔峰' where id = 1;
  1. Executor 调用 InnoDB 引擎接口
  2. InnoDB 发现 id=1 页面不在 Buffer Pool → 随机读盘载入内存
  3. 检查该行是否被未提交事务锁住 → 加 X 锁(当前读)
  4. 生成 undo log(旧版本:name=’慕容复’)
  5. 执行更新:内存中将 name 改为 ‘乔峰’ → 产生 redo log(物理修改记录)
  6. redo log 进入 prepare 阶段(标记为 prepare)
  7. Server 层写入 binlog(逻辑变更记录)
  8. binlog 写完后,InnoDB 收到信号 → redo log 进入 commit 阶段(标记为 commit)
  9. 根据 innodb_flush_log_at_trx_commit 参数决定是否立即刷盘
  10. 返回成功给客户端

这就是传说中的“两阶段提交”,保证 redo log 与 binlog 一致性!

六、崩溃恢复时到底发生了什么?(Crash Recovery)

MySQL 启动时,InnoDB 会执行 crash recovery:

  1. 从 redo log 中找到最后一个 checkpoint
  2. 从 checkpoint 开始顺序扫描 redo log
  3. 遇到 prepare 状态的事务 → 记录下来
  4. 遇到 commit 状态 → 重做(前滚 forward)
  5. 重做完成后,对比 binlog:
  • 如果 binlog 中有该事务 → 没事
  • 如果没有 → 回滚该事务(极少数极端情况)

这样就实现了“要么全做,要么全不做”!

写到这里,基本把 MySQL 最核心的底层原理全部讲透了。

重阳,你现在项目里是用的 MySQL 8.0 还是 8.4?
redo log capacity 配了多大?
有没有遇到过 undo log 膨胀导致磁盘打满的坑?
来聊聊你的真实生产经验~

文章已创建 4206

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部