InnoDB 的 Redo Log、Undo Log 和 MVCC 详解
0.InnoDB存储引擎架构
InnoDB 是 MySQL 的默认存储引擎,具有高可靠性和性能,支持事务和行级锁。
(1)InnoDB架构核心组件
- Buffer Pool(缓冲池):
- 用于缓存表数据和索引,减少磁盘 I/O 操作。
- 包含数据页、索引页、插入缓冲等。
- Redo Log(重做日志):
- 记录事务操作,用于崩溃恢复。
- 提供 WAL(Write-Ahead Logging)机制,确保日志先写磁盘再修改数据。
- Undo Log(回滚日志):
- 用于事务回滚和 MVCC 的实现。
- 每个事务的更新会记录到 Undo Log。
- Adaptive Hash Index(自适应哈希索引):
- 自动生成的哈希索引,提高某些查询的速度。
- Change Buffer(变更缓冲区):
- 暂存对非唯一索引的修改,异步写入磁盘。
- 提高批量写入性能。
- Doublewrite Buffer(双写缓冲):
- 防止单页写入损坏数据,通过两次写操作保障一致性。
2. 事务原理
(1)ACID特性
InnoDB 通过以下机制实现 ACID:
- 原子性(Atomicity): 通过 Undo Log 回滚未完成的事务。
- 一致性(Consistency): 数据在事务之间保持一致。
- 隔离性(Isolation): 支持 4 种隔离级别,防止事务间干扰。
- 持久性(Durability): 使用 Redo Log 保证事务提交后的数据持久化。
(2)事务隔离级别
- READ UNCOMMITTED(未提交读):
- 允许读取未提交的数据,可能出现脏读。
- READ COMMITTED(提交读):
- 只能读取已提交的数据,防止脏读。
- REPEATABLE READ(可重复读):
- 同一事务内多次读取结果一致,防止幻读和不可重复读。
- SERIALIZABLE(可串行化):
- 最高隔离级别,所有事务按顺序执行,防止一切并发问题。
(3)锁机制
InnoDB 提供以下锁:
- 行级锁:
- Record Lock(记录锁): 锁定行记录本身。
- Gap Lock(间隙锁): 锁定索引间隙,防止幻读。
- Next-Key Lock(临键锁): 记录锁 + 间隙锁。
- 意向锁:
- 意向共享锁(IS): 表示事务想对某些行加共享锁。
- 意向排他锁(IX): 表示事务想对某些行加排他锁。
1. 什么是 MVCC?
MVCC(多版本并发控制,Multi-Version Concurrency Control)是一种实现高效并发的机制。
通过维护数据的多个版本,避免了读操作加锁,从而提升数据库性能。
MVCC 是事务隔离实现的关键,特别适用于读多写少的场景(如 OLTP 系统)。
2. MVCC 的核心组件
组件 | 描述 |
---|---|
隐藏列 | InnoDB 每行数据都包含两个隐藏列: |
- DB_TRX_ID :记录最后一次修改该行的事务 ID。 | |
- DB_ROLL_PTR :指向 Undo Log 的指针,用于存储历史版本数据。 | |
Undo Log | - 记录每次更新操作前的旧版本数据(逻辑日志)。 |
- 提供快照数据供快照读(Snapshot Read)使用。 | |
Read View | - 每个事务启动时创建的视图,记录当前活跃事务和其版本信息。 |
- 决定事务可以看到哪些版本的数据。 |
3. 两种读操作:快照读与当前读
(1)快照读(Snapshot Read)
- 定义: 通过 MVCC 机制读取历史版本数据,而不是直接读取当前数据。
- 特点: 不加锁,性能高。
- 适用场景: 普通
SELECT
语句,例如:SELECT * FROM products WHERE stock > 0;
(2)当前读(Current Read)
- 定义: 读取最新数据,并加锁防止其他事务修改。
- 特点: 需要加锁,性能较低。
- 适用场景: 修改或加锁读取,例如:
SELECT * FROM products WHERE id = 1 FOR UPDATE; UPDATE products SET stock = stock - 1 WHERE id = 1;
4. MVCC 的实现原理
(1)隐藏列与版本控制
- 每行记录都包含两个隐藏列:
DB_TRX_ID
:记录最后一次修改该行的事务 ID。DB_ROLL_PTR
:指向 Undo Log,用于找到之前版本的快照数据。
(2)Undo Log 提供快照
- Undo Log 保存每次更新前的数据版本。
- 快照读时,通过
DB_ROLL_PTR
指针找到 Undo Log,返回快照数据。
(3)Read View 决定可见性
-
Read View 内容:
- 当前活跃事务列表: 包含事务启动时所有尚未提交的事务 ID。
- 最小活跃事务 ID(min_trx_id): 活跃事务中最早的事务 ID。
- 下一个分配事务 ID(max_trx_id): 尚未分配的事务 ID。
-
数据版本可见性判断规则:
- 如果
DB_TRX_ID
小于min_trx_id
,表示该版本在事务启动前已提交,对当前事务可见。 - 如果
DB_TRX_ID
大于等于max_trx_id
,表示该版本在事务启动后生成,对当前事务不可见。 - 如果
DB_TRX_ID
在活跃事务列表中,表示该版本尚未提交,对当前事务不可见。
- 如果
(4)隔离级别对快照的影响
- READ COMMITTED: 每次查询生成新的 Read View,读取最新快照数据。
- REPEATABLE READ: 同一事务内的查询使用固定的 Read View,保证多次读取结果一致。
5. Redo Log 和持久性
特性 | 描述 |
---|---|
主要作用 | 保证事务的持久性(Durability),即提交的事务数据即使系统崩溃也不会丢失。 |
工作原理 | - Redo Log 是物理日志,记录数据页的更改。 - 修改先写入 Redo Log,之后再将数据异步刷新到磁盘。 |
WAL机制 | Write-Ahead Logging:先写日志,再写数据。 |
崩溃恢复 | 在数据库重启时,Redo Log 用于重做已提交但未写入磁盘的数据,保证数据一致性。 |
优化参数 | - innodb_log_file_size :控制日志文件大小。- innodb_flush_log_at_trx_commit :控制刷新策略。 |
流程图 | 事务更新 -> 写入 Redo Log -> 提交 -> 异步刷新到数据文件。 |
6. Undo Log 和原子性
特性 | 描述 |
---|---|
主要作用 | 保证事务的原子性(Atomicity),即未完成的事务可以被完全回滚,确保事务操作要么全部完成,要么全部撤销。 |
工作原理 | - Undo Log 是逻辑日志,记录数据行的修改前值。 - 回滚操作依赖 Undo Log 恢复数据到修改前的状态。 |
事务回滚 | 如果事务失败或主动回滚,Undo Log 逐步撤销事务的所有更改。 |
MVCC的支持 | Undo Log 提供之前版本的快照数据,支持 MVCC 实现多版本并发控制。 |
日志类型 | 每次更新生成 Undo Log,写入 DB_ROLL_PTR 指向的 Undo 日志链表。 |
流程图 | 事务更新 -> 写入 Undo Log -> 提交或回滚时使用 Undo Log 还原数据。 |
7. 数据可见性判断示例
假设:
- 当前事务 ID:10。
- 活跃事务列表:[8, 9]。
- 数据行的
DB_TRX_ID
和 Undo Log 如下:
版本号(DB_TRX_ID) | 数据内容 | 事务状态 |
---|---|---|
15 | stock = 50 | 尚未提交,不可见 |
9 | stock = 60 | 活跃事务,不可见 |
7 | stock = 70 | 已提交,可见 |
判断规则:
- 版本
15
的事务未提交,不可见。 - 版本
9
的事务是活跃事务,不可见。 - 版本
7
的事务已提交且早于当前事务启动,可见。
最终结果:stock = 70
。
8. 三者关系总结
组件 | 作用 | 实现机制 | 与其他组件的关系 |
---|---|---|---|
Redo Log | 保证事务持久性(D) | WAL机制:先写日志,再写数据 | 配合事务提交确保数据持久化;与 Undo Log 无直接关联。 |
Undo Log | 保证事务原子性(A);支持 MVCC | 记录修改前的旧值,回滚时使用;提供快照数据支持 MVCC | 支持 MVCC 的快照读;在回滚时恢复修改前数据;回滚时无需 Redo Log。 |
MVCC | 支持高并发读写(隔离性 I) | 依赖 Undo Log 提供历史版本,结合 Read View 实现一致性快照读。 | 依赖 Undo Log 记录旧版本数据;Redo Log 不直接参与 MVCC 的实现。 |