MySQL 核心日志:redo log、undo log 与 binlog + MVCC 深度讲解(2026 年最新视角)
这三兄弟 + binlog 是 MySQL 能做到 crash-safe、可重复读、以及主从复制的绝对核心。我们今天一次性讲透它们底层实现细节、WAL 机制、与 InnoDB 缓冲池的关系、MVCC 精确工作原理。
一、redo log(重做日志)—— 实现 Crash-Safe 的物理日志
核心作用:保证已经提交的事务,即使宕机了,数据也一定能恢复(持久性 Durability)
- 属于 InnoDB 存储引擎层,不是 MySQL Server 层
- 是物理日志,记录的是“在某个数据页上做了什么修改”,例如:
- 对页号 5 的偏移量 100 处改成了“姑苏慕容复”
- 采用 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 的两条核心刷盘规则(面试必问):
- 当 redo log 持久化到磁盘后,脏页才可以被淘汰出 buffer pool
- checkpoint 推进时,会把 checkpoint_lsn 之前的脏页刷到磁盘
二、undo log(回滚日志)—— 实现原子性 + MVCC 的基石
两大作用:
- 事务回滚(Rollback)
- 多版本并发控制(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(大多数人以为是间隙锁,其实不是)
每行记录的隐藏列(系统列):
- DB_TRX_ID(6字节):最近修改该行的事务 ID
- DB_ROLL_PTR(7字节):回滚指针,指向 undo log 中的旧版本
- 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,我们判断:
- 如果 trx_id == creator_trx_id → 自己改的,肯定可见
- 如果 trx_id < min_trx_id → 已经提交的旧版本,肯定可见
- 如果 trx_id >= max_trx_id → 未来事务,肯定不可见
- 如果 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;
- Executor 调用 InnoDB 引擎接口
- InnoDB 发现 id=1 页面不在 Buffer Pool → 随机读盘载入内存
- 检查该行是否被未提交事务锁住 → 加 X 锁(当前读)
- 生成 undo log(旧版本:name=’慕容复’)
- 执行更新:内存中将 name 改为 ‘乔峰’ → 产生 redo log(物理修改记录)
- redo log 进入 prepare 阶段(标记为 prepare)
- Server 层写入 binlog(逻辑变更记录)
- binlog 写完后,InnoDB 收到信号 → redo log 进入 commit 阶段(标记为 commit)
- 根据 innodb_flush_log_at_trx_commit 参数决定是否立即刷盘
- 返回成功给客户端
这就是传说中的“两阶段提交”,保证 redo log 与 binlog 一致性!
六、崩溃恢复时到底发生了什么?(Crash Recovery)
MySQL 启动时,InnoDB 会执行 crash recovery:
- 从 redo log 中找到最后一个 checkpoint
- 从 checkpoint 开始顺序扫描 redo log
- 遇到 prepare 状态的事务 → 记录下来
- 遇到 commit 状态 → 重做(前滚 forward)
- 重做完成后,对比 binlog:
- 如果 binlog 中有该事务 → 没事
- 如果没有 → 回滚该事务(极少数极端情况)
这样就实现了“要么全做,要么全不做”!
写到这里,基本把 MySQL 最核心的底层原理全部讲透了。
重阳,你现在项目里是用的 MySQL 8.0 还是 8.4?
redo log capacity 配了多大?
有没有遇到过 undo log 膨胀导致磁盘打满的坑?
来聊聊你的真实生产经验~