MVCC
约 1113 字大约 4 分钟
2025-01-15
1.MVCC
MVCC,多版本并发控制,允许多个事务同时对数据库进行读写操作,解决了一个数据的多版本读写冲突;传统的锁机制可以实现并发控制,但会导致阻塞和死锁等问题
核心思想:在数据库中,通过 undo log 维护多个数据版本,并根据事务的隔离级别来决定哪个版本数据对特定事务是可见的
MVCC 实现的三个重要部分:
- 三个隐藏字段:隐藏主键、创建该数据的事务 id 、回滚指针(指向上一个版本的指针)
- undo log 版本链
- readView 快照

2.ReadView 快照
ReadView,一个保存事务 ID 的 list 列表。记录的是本事务执行时,MySQL 还有哪些事务在执行,且还没有提交。同时,快照一种数据结构,包含四个字段:
- creator_trx_id:ReadView 创建者的事务编号
- m_ids:当前活跃的事务编号集合
- min_trx_id:最小活跃事务编号
- max_trx_id:预分配事务编号,即当前最大事务编号+1
不同隔离级别下快照生成的时机:
- RC(读已提交):每一次查询 ,都生成一个快照 ReadView
- RR(可重复读):开启一个事务之后,只有第一个查询语句才会生成一个快照,此后读的都是快照中的数据,直到事务提交
- Serializable(可序列化):快照读退化成当前读(加锁,阻塞,读取到的是最新的数据)
根据 ReadView 快照访问 undo log 版本链数据的规则:
- 若该版本的创建事务 id 等于当前事务 id ?可以访问该版本,因为数据是当前这个事务更改的;
- 若该版本的创建事务 id 小于 快照中 最小活跃事务编号?可以访问该版本,因为数据已经提交了;
- 若该版本的创建事务 id 大于 快照中 预分配事务 id?不可以访问该版本,因为该事务修改的数据是在 ReadView 生成后才开启的;
- 若 快照中最小活跃事务编号 <= 该版本的创建事务 id <= 预分配事务 id 并且 该版本的创建事务 id 不在活跃事务编号集合中,可以访问该版本,因为该数据已经提交;
3.快照读/当前读
快照读:
最普通的 Select 查询 SQL 语句
读取的是数据的可见版本,有可能是历史数据、当前版本,不加锁,是非阻塞读
底层依赖:当执行“快照读”SQL 语句时,依据 ReadView(快照)来提取数据
当前读
- 读取的是当前记录的最新版本,读取的时候需要保证其他并发事务不能修改当前记录,对当前记录加锁
- 例子:Insert、Update、Delete、Select... for update(写锁)、Select... lock in share mode(读锁)
4.举例
4.1 RC(读已提交)
- 其中事务 4 的两次快照读均会产生 ReadView,如下:

- 分析第一个 ReadView:

- 分析第二个 ReadView:

小结:
- 在 RC(读已提交)的事务隔离级别下,同一事务的两次快照读均会产生两个快照(ReadView);
- 第一个快照读读取的数据是 事务一修改并提交的数据:张三
- 第二个快照读读取的数据是 事务二修改并提交的数据:张小三
- 同一事务的两个不同 select(快照读)读取的数据不一样,产生不可重复读现象
思考:应该怎么解决?
解决:设置隔离级别为 RR(可重复读),同一事务从始至终只会生成一个快照
4.2 RR(可重复读)
- 隔离级别为 RR(可重复读),同一事务从始至终只会生成一个快照,即不会产生 不可重复读问题

5.RR 能解决幻读问题吗
结论:RR(可重复读)可以解决一部分幻读问题
原因:
同一事务的连续多次快照读,ReadView 会产生复用,没有幻读问题
特例:当两次快照读之间存在当前读,ReadView 会重新生成,导致幻读问题

版权所有
版权归属:haipeng-lin