在 MySQL 中,数据的读写并不是直接对着磁盘干,而是通过一套复杂的日志系统来保证性能与安全。如果你执行一条 UPDATE 语句,虽然你没看到磁盘在狂转,但后台已经经历了一场关于三大日志的“接力赛”。
一、 为什么不能直接写磁盘?(Buffer Pool 与 WAL)
在理解日志之前,我们要先看 Buffer Pool(缓冲池)。
- 痛点:磁盘随机读写非常慢。如果每次改数据都直接写磁盘,数据库的并发量根本上不去。
- 方案:MySQL 会先把数据读到内存里的 Buffer Pool 中。修改时,也是先改内存。
- 新问题:万一内存改完,还没来得及同步到磁盘就断电了,数据不就丢了吗?
为了解决这个问题,MySQL 引入了 WAL 机制 (Write-Ahead Logging) ,即:先写日志,再写磁盘。而这个日志,就是我们今天要说的第一个主角——Redo Log。
二、 三大日志各司其职
我们可以把数据库比作一个大型超市的收银系统。
1. Undo Log(回滚日志):事务的“后悔药”
-
比喻:相当于操作记录的“撤销”键。
-
作用:
- 原子性:如果你改数据改到一半事务失败了,Undo Log 记录了你改之前长什么样,直接帮你“逆向操作”回去。
- MVCC(多版本并发控制) :它保存了数据的历史版本,让其他事务能看到改之前的数据(快照读)。
-
触发:只要事务开始修改,就会先生成 Undo Log。
2. Redo Log(重做日志):崩溃恢复的“保险”
-
比喻:收银员手里的“临时小账本”。
-
特性:
- 物理日志:记录的是“在某个数据页的某个位置改了什么”。
- 顺序写:日志是追加写入的,比随机找磁盘位置快得多。
-
核心作用:保证 Crash-Safe(崩溃安全)。即使数据库宕机,重启后只要看一眼 Redo Log,就能把 Buffer Pool 里没来得及刷盘的数据恢复出来。
3. Binlog(归档日志):集群同步的“全量录像”
-
比喻:超市总部的“全天交易流水明细”。
-
特性:
- 逻辑日志:记录的是 SQL 语句或行数据的变更(比如:把 ID=1 的余额加 100)。
- 全量保存:Redo Log 是循环覆盖写的(旧的会被删),而 Binlog 是追加写的,保存了数据库所有历史操作。
-
核心作用:主从复制和数据恢复(找回误删的数据)。
三、 深度解析:两阶段提交(2PC)
这是面试最常问的点:既然有了 Redo Log 保证安全,为什么还要 Binlog?为什么写日志还要分两步?
如果 Redo Log 记录了“已修改”,但 Binlog 还没来得及记,主库重启恢复了,但从库通过 Binlog 同步时就会少这一条数据,导致主从不一致。
为了解决这个问题,MySQL 使用了“两阶段提交”:
-
Prepare 阶段:
- 执行器先改内存,然后 InnoDB 引擎将修改记录写入 Redo Log,并将状态标记为
prepare(准备就绪)。
- 执行器先改内存,然后 InnoDB 引擎将修改记录写入 Redo Log,并将状态标记为
-
写 Binlog:
- 执行器将操作写入 Binlog,并刷到磁盘。
-
Commit 阶段:
- 执行器调用引擎的提交事务接口,将 Redo Log 的状态改为
commit(已提交)。
- 执行器调用引擎的提交事务接口,将 Redo Log 的状态改为
异常场景模拟:
- 如果在第 2 步写 Binlog 之前宕机:重启后发现 Redo Log 只是
prepare,且 Binlog 里没这条记录,直接回滚。 - 如果在第 2 步写完 Binlog 之后宕机:重启后发现虽然 Redo Log 是
prepare,但 Binlog 里已经有记录了。此时认为事务有效,自动提交。
结论:两阶段提交确保了 Redo Log 和 Binlog 在逻辑上是原子性的,保证了库里数据和同步出去的数据一模一样。
四、 总结
| 特性 | Undo Log | Redo Log | Binlog |
|---|---|---|---|
| 层级 | InnoDB 引擎层 | InnoDB 引擎层 | MySQL Server 层 |
| 记录内容 | 逻辑操作(逆向SQL) | 物理页修改 | 逻辑操作(SQL/行记录) |
| 主要目的 | 事务回滚 (ACID-A) | 崩溃恢复 (Crash-Safe) | 主从复制、全量恢复 |
| 写入时机 | 修改前 | 持续写入(Prepare/Commit) | 事务提交时 |