一、生成文件
InnoDB
-
生成**
xxx.ibd**文件,用于存储所有数据库对象的元数据。比如数据信息,索引信息 -
ibdata1 -
日志:
undo_*.log,#ib_redo*。从ibdata1中独立出来了
MyISAM
-
*.MYD:存储所有数据行 -
*.MYI:存储所有索引 -
*.sdi:用于存储非 InnoDB 表的元数据
二、日志操作
undo-log:主要用于实现事务ACID原则中的原子性和MVCC机制。redo-log:主要用于实现事务原则中的持久性,确保事务提交后就不会丢失。bin-log:主要结合redo-log实现事务原则中的一致性,确保事务提交前后,数据的一致。
1️⃣ Undo Log(回滚日志)
作用:记录事务修改前的旧值,用于事务回滚,同时支持 MVCC 快照读。
- 逻辑层面的日志,关注“如何还原数据”,不会被其他事务看到。
案例 1:UPDATE
UPDATE user SET name='B' WHERE id=1;
假设表 user 原数据:
| id | name |
|---|---|
| 1 | A |
Undo Log 记录内容(逻辑示例) :
事务ID: 100
操作类型: UPDATE
表: user
行主键: 1
旧值: name='A'
DB_ROLL_PTR: null
作用:如果事务回滚,将 name 从 'B' 还原成 'A'。
案例 2:DELETE
DELETE FROM user WHERE id=2;
假设删除前数据:
| id | name |
|---|---|
| 2 | C |
Undo Log 记录内容:
事务ID: 101
操作类型: DELETE
表: user
行主键: 2
旧值: id=2, name='C'
DB_ROLL_PTR: null
作用:回滚时会重新插入这行数据。
案例 3:SELECT
SELECT * FROM user WHERE id=1;
- Undo Log 不生成,因为没有修改数据。
- 对于快照读,如果读到未提交版本,它会利用已存在的 Undo Log 回溯到可见版本。
2️⃣ Redo Log(重做日志)
作用:记录物理修改操作,用于崩溃恢复。
- 顺序写磁盘,保证持久性。
- 关注“这页的哪些字节被改成什么值”,粒度比 Undo 更接近页级或行级的物理存储。
案例 1:UPDATE
Redo Log 内容示意(逻辑化):
LSN: 120
事务ID: 100
表: user
页号: 7
行偏移: 1024
修改: name 列从 'A' 改为 'B'
意义:我对磁盘的第7页的1024行的数据进行了修改。后面恢复的时候直接去修改磁盘就行,更快
作用:数据库崩溃后,重做这个操作,把页的内容改回 'B'。
案例 2:DELETE
Redo Log 内容示意:
LSN: 130
事务ID: 101
表: user
页号: 9
行偏移: 2048
删除操作:删除 id=2 行
作用:崩溃后重放日志即可把行从页中删除。
案例 3:SELECT
- Redo Log 不生成,因为没有修改磁盘页。
3️⃣ Binlog(二进制日志)
作用:记录逻辑 SQL 操作,主要用于主从复制和备份恢复。
- 粒度是 SQL 或行级逻辑(取决于 binlog_format:STATEMENT / ROW / MIXED)。
- 作用对象是 全局可见数据,Undo/Redo 不关心的。
案例 1:UPDATE
UPDATE user SET name='B' WHERE id=1;
- Row 格式 binlog 记录:
表: user 行ID: 1 旧值: name='A' 新值: name='B'
- Statement 格式 binlog 记录:
UPDATE user SET name='B' WHERE id=1;
案例 2:DELETE
DELETE FROM user WHERE id=2;
- Row 格式 binlog:
表: user
行ID: 2
操作: DELETE
旧值: id=2, name='C'
- Statement 格式 binlog:
DELETE FROM user WHERE id=2;
案例 3:SELECT
- Binlog 不生成,因为 SELECT 不修改数据。
- 即便使用审计日志,也属于独立日志,不算 redo/undo/binlog 范畴。
🔑 总结对比
| 日志类型 | 粒度 | 作用 | 生成时机 | 示例作用 |
|---|---|---|---|---|
| Undo Log | 行级,逻辑 | 回滚、MVCC | 修改数据前 | UPDATE前的旧值 |
| Redo Log | 页级/行级,物理 | 崩溃恢复 | 修改内存后 | Buffer Pool修改的物理页 |
| Binlog | SQL / 行 | 主从复制、备份 | 提交时 | SQL语句或新旧行数据 |
可以把它想象成:
-
Undo Log:时间的倒带胶卷 → 回到修改前
-
Redo Log:物理重做录像 → 崩溃重放
-
Binlog:广播录像 → 其他数据库看到这个操作
三、日志写入顺序
1️⃣ 日志写入过程:
1️⃣ 执行SQL语句,修改Buffer Pool中的页(内存中)
2️⃣ 记录Undo Log(为回滚准备)
3️⃣ 生成Redo Log(记录物理修改)
4️⃣ Redo Log写入磁盘的log buffer(可能异步flush)
5️⃣ 当执行COMMIT:
a. Redo Log刷盘(持久化)
b. Binlog写入binlog cache
c. Binlog刷盘
d. Redo标记为commit
2️⃣ 崩溃恢复:用时间线直观表示
时间线 →
┌────────────────────────────────────────────────────────────┐
│ MySQL 事务提交过程 │
└────────────────────────────────────────────────────────────┘
↓ ↓ ↓
[1] 写redo(prepare)→[2]写binlog→[3]redo标记commit
┌──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐
│ 宕机点1:redo prepare后崩溃 │ 宕机点2:binlog写完崩溃 │ 宕机点3:redo commit后崩溃 │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ redo = prepare │ redo = prepare │ redo = commit │
│ binlog = 无 │ binlog = 有 │ binlog = 有 │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ 恢复时:回滚 │ 恢复时:提交(重做) │ 恢复时:已完成 │
│ 原因:无 binlog,认为未提交 │ 原因:有 binlog,推断成功提交│ 原因:redo commit 已持久化 │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
3️⃣为什么需要redo+binlog双重验证
-
考虑主从一致性
来看这个经典时间线:1️⃣ 写 redo log (prepare) 并刷盘 ✅ 2️⃣ 写 binlog,但写到一半宕机 ❌如果让 “prepare = commit”,那重启后 MySQL 就会:
- 根据 redo log 把修改数据重做出来;
- 但是 binlog 没写完(或没写入),
- 主库数据更新了,从库没有同步;
- 这就出现 主从不一致。
因此:
prepare 不是“提交完成”,而是“随时可以提交,也可以回滚”的安全点。
只有当 binlog 也写成功了,InnoDB 才把 redo 的状态改成 commit
-
Q:为什么不能先写binlog,再写redo
考虑到binlog是sever层,redo是引擎层的,那么完全可以当命令传到的时候就先进行记录。
但是这样会出现另一个灾难场景:
1️⃣ binlog 已写完 ✅
2️⃣ redo log 还没写入磁盘 ❌(宕机)
结果是:- Binlog 说:“我提交成功了”
- Redo log 说:“我没保存修改”
- 主从同步执行时,从库照着 binlog 重放一遍;
- 但主库自己丢失了数据。主从再次不一致。