MySQL 中的三大日志(binlog 、redo log 、undo log )是数据库能够实现 ACID 特性 (特别是原子性、持久性)以及主从复制 、数据恢复的关键。
为了让你条理清晰地理解,我们可以从作用、所属层级、记录内容这几个维度来拆解。
我们可以把它们比作:
- binlog :"历史档案"(记录所有发生过的事,用于归档和传给别人)。
- redo log :"草稿本/记账板"(防止突然断电,用于崩溃恢复)。
- undo log :"后悔药"(事务失败了要回滚,或者看过去的数据)。
一、 redo log(重做日志)------ 保证"持久性" (Durability)
1. 核心作用:Crash-safe(崩溃恢复)
MySQL 为了性能,更新数据时是先更新内存(Buffer Pool),然后异步刷到磁盘。如果内存更新了但还没刷盘时数据库宕机,数据就丢了。
redo log 的作用就是:只要事务提交成功,即使数据库立刻宕机,重启后也能通过 redo log 找回数据。 这就是 WAL 技术(Write-Ahead Logging,先写日志,再写磁盘)。
2. 所属层级
- 特属于 InnoDB 存储引擎。
3. 记录内容(物理日志)
- 它记录的是物理修改。比如:"在第 X 号表空间的第 Y 号数据页上,偏移量 Z 处做了什么修改"。
4. 写入方式(循环写)
- redo log 的文件大小是固定的(例如一组 4 个文件,每个 1GB)。
- 它像一个圆环,写满了一个就回到开头覆盖写。
- write pos:当前记录的位置。
- checkpoint:当前已经擦除(同步到磁盘)的位置。
- 如果 write pos 追上了 checkpoint,说明空间满了,必须停下来把脏页刷到磁盘,腾出空间。
二、 undo log(回滚日志)------ 保证"原子性" (Atomicity)
1. 核心作用:事务回滚 & MVCC
- 事务回滚 :如果一个事务执行到一半出了错,或者用户执行了
ROLLBACK,MySQL 需要把数据恢复到事务开始前的样子。 - MVCC(多版本并发控制):当读取记录时,若该记录被其他事务占用,可以通过 undo log 读取之前的版本(快照读),实现非阻塞读。
2. 所属层级
- 特属于 InnoDB 存储引擎。
3. 记录内容(逻辑日志)
- 它记录的是逻辑相反操作。
-
- 你执行
INSERT,它记录一条DELETE。 - 你执行
DELETE,它记录一条INSERT。 - 你执行
UPDATE,它记录一条反向的UPDATE。
- 你执行
4. 生命周期
- 事务开始前产生,事务提交后并不会立刻删除(因为 MVCC 可能还需要用),而是放入待删除列表,由 Purge 线程清理。
三、 binlog(归档日志)------ 用于"复制"和"恢复"
1. 核心作用:主从复制 & 数据恢复
- 主从复制:Master 端把 binlog 发送给 Slave 端,Slave 重放这些日志,从而保证主从数据一致。
- 数据恢复:如果你误删库了,可以通过 binlog 把数据库恢复到某个时间点(PITR - Point In Time Recovery)。
2. 所属层级
- 属于 MySQL Server 层(所有存储引擎共用,MyISAM 也有)。
3. 记录内容(逻辑日志)
- 记录的是 SQL 语句的原始逻辑。
- 格式有三种:
-
Statement:记录 SQL 语句原文(可能导致主从不一致,如使用了now()函数)。Row:记录每一行具体被修改成什么样(最安全,但日志量大)。Mixed:混合模式。
4. 写入方式(追加写)
- 追加写(Append-only)。文件写到一定大小后会切换到下一个文件,不会覆盖以前的日志。
四、 重点对比:redo log vs binlog
这是面试中最常问的区别,必须清晰区分:
|----------|-----------------------|-----------------------|
| 特性 | redo log (重做日志) | binlog (归档日志) |
| 归属 | InnoDB 引擎特有 | MySQL Server 层 (引擎通用) |
| 内容类型 | 物理日志 (数据页改了啥) | 逻辑日志 (SQL语句/行变化) |
| 主要作用 | 崩溃恢复 (Crash-safe) | 主从复制 、历史恢复 |
| 写入方式 | 循环写 (空间固定,会覆盖) | 追加写 (文件切换,不覆盖) |
| 持久性 | 事务提交必须写 (保证不丢) | 事务提交必须写 (保证同步) |
五、 进阶:它们是如何配合的?(两阶段提交)
当我们要更新一条数据时,redo log 和 binlog 是如何保证一致性的?这就涉及到了两阶段提交 (Two-Phase Commit, 2PC)。
假设执行 UPDATE user SET name='A' WHERE id=1;
- 执行阶段:InnoDB 将内存中的数据更新。
- redo log prepare(预提交) :InnoDB 写 redo log,标记状态为
prepare。 - 写 binlog:Server 层写入 binlog 并持久化到磁盘。
- redo log commit(提交) :InnoDB 将 redo log 标记为
commit。
为什么要这么麻烦?
是为了防止由宕机引发的 "日志不一致"。
- 如果先写 redo log,再写 binlog:
-
- redo log 写完,机器断电,binlog 没写。
- 重启后,redo log 恢复了数据(主库有数据),但 binlog 没记录(从库没数据)。主从不一致。
- 如果先写 binlog,再写 redo log:
-
- binlog 写完,机器断电,redo log 没写。
- 重启后,主库数据丢了(redo log 没刷盘),但从库通过 binlog 同步了该数据。主从不一致。
两阶段提交保证了:要么两个日志都成功,要么都失败,确保主库数据和从库(以及历史归档)的一致性。
总结
- undo log:原子性。如果我要反悔(回滚),或者想看旧数据(MVCC),找它。
- redo log:持久性。如果服务器挂了,重启时要恢复内存里没来得及刷盘的数据,找它。
- binlog:归档与复制。如果我要把数据同步给从库,或者恢复到上个月的状态,找它。