MySQL redo log、undo log 和 binlog 分别有什么作用?
简化版
redo log 保证崩溃恢复,让已提交事务的修改不会丢;undo log 保存旧版本,用于事务回滚和 MVCC;binlog 记录逻辑变更,用于复制和数据恢复。redo/undo 是 InnoDB 存储引擎层日志,binlog 是 MySQL Server 层日志。
详细版
三种日志职责不同:
| 日志 | 所属层次 | 主要作用 | 典型关键词 |
|---|---|---|---|
| redo log | InnoDB | 崩溃恢复,保证持久性 | WAL、crash-safe |
| undo log | InnoDB | 回滚、MVCC 历史版本 | 版本链、Read View |
| binlog | Server 层 | 主从复制、时间点恢复 | 逻辑日志、复制 |
更新一行数据时,InnoDB 不一定立刻把数据页刷到磁盘,而是先写 redo log。崩溃后可以用 redo log 重放已提交修改。事务回滚时,需要 undo log 把数据恢复到旧值。主从复制时,从库读取主库 binlog 并重放变更。
为了保证 redo log 和 binlog 的一致性,MySQL 提交事务时会使用两阶段提交:先准备 redo,再写 binlog,最后提交 redo。
完整版教学
一、redo log:解决崩溃后数据不丢
InnoDB 使用 WAL 思想,也就是 Write-Ahead Logging:修改数据页前,先把修改记录写到日志里。这样就不需要每次事务提交都立刻刷完整数据页。
如果数据库崩溃,内存里的脏页可能还没刷盘。重启后,InnoDB 可以根据 redo log 把已提交事务的修改恢复出来。这就是 redo log 的核心价值:保证事务的持久性。
举个数字例子:更新一行余额时,完整数据页可能是 16KB,而这次变更的 redo 记录可能只描述“某个页的某个位置怎么改”。如果每次提交都刷 16KB 数据页,随机 I/O 压力很大;先顺序写 redo log,再让后台慢慢刷脏页,就能把随机写转成更友好的日志写。崩溃恢复时,redo log 会把已经提交但没刷盘的数据页修改补回来。
事务提交前后:
修改 Buffer Pool 中的数据页 -> 写 redo log -> 提交返回
后台稍后把脏页刷到磁盘
崩溃后:用 redo log 重放已提交修改
二、undo log:解决回滚和一致性读
undo log 记录的是修改前的旧版本。事务执行失败或主动回滚时,InnoDB 可以用 undo log 把数据恢复到修改前。
undo log 还有另一个高频作用:支撑 MVCC。普通查询需要读历史版本时,会沿着记录上的回滚指针去 undo log 里找旧版本,再根据 Read View 判断可见性。
所以 undo log 不是只服务回滚,它也是快照读能成立的重要基础。
例如 age 从 18 更新到 19,undo log 里会保留“改回 18”所需的信息。事务回滚时,InnoDB 根据 undo 把值恢复;另一个事务做快照读时,如果 19 这个版本对它不可见,也能沿版本链读到 18。长事务会让旧版本保留更久,因此 undo 空间和 purge 清理也会受到影响。
| 作用 | undo log 怎么参与 |
|---|---|
| 事务回滚 | 根据旧值或反向操作恢复修改前状态 |
| MVCC | 形成历史版本链,供快照读查找可见版本 |
| 一致性读 | 配合 Read View 判断哪个旧版本可见 |
三、binlog:解决复制和恢复
binlog 是 MySQL Server 层的二进制日志,记录数据库发生的逻辑变更。它不只服务 InnoDB,理论上其他存储引擎也可以使用。
binlog 常见用途:
- 主从复制:从库读取主库 binlog,再执行对应变更;
- 数据恢复:结合备份和 binlog 做时间点恢复;
- 数据订阅:一些同步工具会消费 binlog。
redo log 偏物理恢复,binlog 偏逻辑变更记录。两者定位不同,不能互相替代。
binlog 的价值在于它是 Server 层的变更历史。主从复制时,从库读取主库 binlog 并重放;做时间点恢复时,可以先恢复一个全量备份,再把备份时间点之后的 binlog 按顺序重放到指定时刻。因为它面向逻辑变更和复制链路,所以即使 InnoDB 有 redo log,也仍然需要 binlog。
恢复到 10:30 的思路:
1. 恢复凌晨 02:00 的全量备份
2. 重放 02:00 到 10:30 之间的 binlog
3. 跳过 10:31 的误删除
记忆钩子:redo 管 InnoDB 自己崩溃后怎么站起来,binlog 管整套 MySQL 变更怎么传播和回放。
四、为什么需要两阶段提交
一条更新既要写 redo log,也要写 binlog。如果只写成功一个就崩溃,会出现问题:
- redo 有、binlog 没有:主库恢复后有这次修改,但从库收不到;
- binlog 有、redo 没有:从库可能执行了变更,主库恢复后却没有。
两阶段提交的目标是让两份日志在事务提交上保持一致。简化流程:
- redo log 写入 prepare 状态;
- 写 binlog;
- redo log 写入 commit 状态。
崩溃恢复时,MySQL 可以根据 redo 和 binlog 的状态判断事务是否应该提交,避免主从不一致。
可以把它看成 redo 和 binlog 之间的一次对账协议。事务先把 redo 写到 prepare,表示 InnoDB 已经准备好提交;再写 binlog,保证复制和逻辑恢复能看到这次变更;最后把 redo 标记为 commit。崩溃恢复时,如果看到 redo prepare 且 binlog 完整,就可以提交;如果 binlog 不完整,就回滚或不提交,从而避免主库和从库对同一事务理解不同。
| 崩溃点 | 恢复判断 |
|---|---|
| redo prepare 前崩溃 | 事务未准备好,通常回滚 |
| redo prepare 后、binlog 前崩溃 | binlog 没有完整记录,不能当作已提交 |
| binlog 完成后、redo commit 前崩溃 | 可根据完整 binlog 提交 redo |
| redo commit 后崩溃 | 事务已提交,redo 可用于恢复 |
五、三种日志的层次和生命周期
redo log 和 undo log 属于 InnoDB 存储引擎层,binlog 属于 MySQL Server 层。redo log 通常是循环写的物理变更日志,空间会复用;binlog 是追加的逻辑日志,保留策略常用于复制和恢复;undo log 在没有事务需要旧版本后,才可被 purge 清理。
这个层次关系能解释很多问题:只开启 binlog 不能保证 InnoDB 崩溃恢复,因为崩溃恢复需要 redo;只有 redo 也不能做主从复制,因为从库需要可传播的逻辑变更;undo 不能拿来做复制,因为它服务的是回滚和历史版本,不是外部变更流。
六、一次 update 中三种日志怎么配合
一条更新大致可以这样理解:
1. 记录 undo:保存旧版本,支持回滚和 MVCC
2. 修改内存数据页:产生脏页
3. 写 redo prepare:保证崩溃恢复有依据
4. 写 binlog:保证复制和逻辑恢复有依据
5. 写 redo commit:完成两阶段提交
6. 后台刷脏页:把数据页最终落盘
这里的顺序是帮助理解的简化模型,真实实现还涉及刷盘策略、组提交、事务提交阶段等细节。面试回答到这个程度,重点是说清每种日志在事务正确性中的职责,而不是背内部函数名。
七、常见误区与追问
- 误区:binlog 能替代 redo log。 binlog 是 Server 层逻辑日志,主要用于复制和时间点恢复,不负责 InnoDB 数据页级崩溃恢复。
- 误区:redo log 能替代 binlog。 redo log 是 InnoDB 物理恢复日志,不适合作为主从复制和逻辑恢复的通用变更流。
- 误区:undo log 只用于 rollback。 undo log 同时支撑 MVCC,普通快照读可能通过 undo 版本链读取历史版本。
- 误区:事务提交成功代表数据页已经刷盘。 InnoDB 依靠 WAL 先保证日志持久,再由后台刷脏页,提交不等于完整数据页立刻落盘。
- 追问:两阶段提交解决什么问题? 它协调 redo 和 binlog 的提交状态,避免主库崩溃恢复结果与复制日志不一致。
- 追问:三种日志分别属于哪一层? redo 和 undo 属于 InnoDB 存储引擎层,binlog 属于 MySQL Server 层。
八、加强记忆
redo log 管崩溃恢复,确保已提交修改能在重启后找回来;undo log 管回滚和 MVCC,保存旧版本让事务能撤销、快照读能找历史;binlog 管复制和逻辑恢复,把 Server 层变更历史传给从库或恢复工具。redo/undo 是 InnoDB 的内部事务基础,binlog 是 MySQL 层的外部变更记录;一次提交靠两阶段提交把 redo 和 binlog 对齐,避免主库恢复和从库复制出现两套结果。