MVCC(多版本并发控制)机制
1. MVCC(多版本并发控制)机制
MVCC 的核心就是 Undo Log+ Read View,“MV”就是通过 Undo Log 来保存数据的历史版本,实现多版本的管理,“CC”是通过 Read View 来实现管理,通过 Read View 原则来决定数据是否显示。同时针对不同的隔离级别,Read View 的生成策略不同,也就实现了不同的隔离级别。
1.1. MVCC的概念
MVCC的概念:
MVCC(Multiversion Concurrency Control,多版本并发控制)是一种基于乐观锁思想实现事务隔离的技术,核心是:
- 保存数据的多个版本(历史快照)
- 读取时通过版本号判断是否可见,无需加锁
MVCC解决的问题:
- 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
- 降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
- 解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。
快照读vs当前读:
类型 | 描述 | 是否加锁 |
快照读 | 读取的是历史版本(不加锁) | ❌ |
当前读 | 读取的是最新版本数据(加锁) | ✅ |
快照读示例:
SELECT * FROM player WHERE ... -- 快照读(不加锁)
当前读示例:
SELECT * FROM player LOCK IN SHARE MODE; -- 加锁读
UPDATE player SET ... -- DML 操作
1.2. MVCC 的实现原理(InnoDB中)
当查询一条记录的时候,系统如何通过多版本并发控制技术找到它:
- 首先获取事务自己的版本号,也就是事务 ID;
- 获取 Read View;
- 查询得到的数据,然后与 Read View 中的事务版本号进行比较;
- 如果不符合 Read View 规则,就需要从 Undo Log 中获取历史快照;
- 最后返回符合规则的数据。
InnoDB中存储记录的多个版本的方法:
1. 事务版本号(Transaction ID)
- 每个事务启动时,InnoDB 分配唯一的递增事务 ID。
- 用于判断操作先后顺序。
2. 行记录的隐藏列
InnoDB 的叶子段存储了数据页,数据页中保存了行记录,而在行记录中有一些重要的隐藏字段。
InnoDB 为每条记录维护隐藏字段:
字段名 | 含义 |
| 隐藏行 ID,用于创建聚集索引 |
| 最后修改该行记录的事务 ID |
| 回滚指针,指向 Undo Log 的位置 |
3. Undo Log(撤销日志)
- 用于保存行记录的历史版本。
- 以链表形式连接历史版本,支持回滚和快照读取。
Read View 的作用与原理
1. Read View 定义:
- 一个事务启动时生成的视图,记录当前活跃事务的 ID 列表。
- 用于判断某行记录是否对当前事务可见。
2. Read View主要字段:
字段 | 含义 |
| 当前活跃事务 ID 集合(未提交事务) |
| trx_ids 中最大事务 ID |
| trx_ids 中最小事务 ID |
| 当前事务 ID(即创建此 Read View 的事务) |
3. Read View的可见性判断规则:
- 若
T < up_limit_id
→ 可见(提交早) - 若
T > low_limit_id
→ 不可见(提交晚) - 若
up_limit_id < T < low_limit_id
: - T ∈ trx_ids → 不可见(事务未提交)
- T ∉ trx_ids → 可见(事务已提交)
4. 事务隔离级别下 Read View 行为
隔离级别 | Read View 获取频率 | 是否避免不可重复读 |
读已提交 | 每次 SELECT 创建新 Read View | ❌ |
可重复读 | 第一次 SELECT 后复用 Read View | ✅ |
1. 在隔离级别为读已提交(Read Commit)时,一个事务中的每一次 SELECT 查询都会获取一次 Read View。
如果 Read View 不同,就可能产生不可重复读或者幻读的情况。
2. 当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View。
1.3. InnoDB解决幻读的方法
在可重复读的情况下,InnoDB 可以通过 Next-Key 锁 +MVCC 来解决幻读问题。
出现幻读的原因是在读已提交的情况下,InnoDB 只采用记录锁(Record Locking)。
InnoDB 三种行锁的方式:
- 记录锁:针对单个行记录添加锁。
- 间隙锁(Gap Locking):可以帮我们锁住一个范围(索引之间的空隙),但不包括记录本身。采用间隙锁的方式可以防止幻读情况的产生。
- Next-Key 锁:帮我们锁住一个范围,同时锁定记录本身,相当于间隙锁 + 记录锁,可以解决幻读的问题。
在隔离级别为可重复读时,InnoDB 会采用 Next-Key 锁的机制,帮我们解决幻读问题。