本文正在参加「技术专题19期 漫谈数据库技术」活动
MVCC详解
本文主要记录下Mysql的MVCC是怎么实现可重复读的,MVCC(Multi-Version Concurrency Control)叫做多版本并发控制,那么顾名思义就是通过多版本的机制来实现可重复读。
MVCC解决了什么问题?
MVCC其实是为了解决读写冲突的问题,但是解决读写冲突的办法有很多,其实更确切的来讲MVCC解决了再不加锁的情况下解决读写冲突的问题。我们都知道锁是很耗费开销的和影响性能的,因此MVCC帮助我们解决了性能的问题,但是MVCC并不能解决所有读写冲突的场景RR级别下的非唯一索引的幻读。
隐藏列
MVCC通过隐藏列来记录当前事务ID和回滚指针来实现多版本的管理。
- DB_TRX_ID:讲数据变成处理成当前状态的事务id
- DB_ROLL_PTR:回滚指针指向当前数据的上一个版本
快照读和当前读
MVCC通过隐藏列来实现多版本的控制,那么快照读和当前读就是对于这种方式的具体呈现,MVCC可以通过当前读和快照读来向客户端返回对应版本的数据。
- 当前读: 读取当前数据的最新版本,因为要确保读取到最新的版本数据所以需要对数据加锁(select for update);
- 快照读: 其实在我们正常的SELECT查询大多都是快照读,在进行select的时候,读取MVCC中的一个版本,相当于读取了一个快照,根据该版本的记录事务Id和MVCC的多版本中进行比较,返回对应的记录,所以叫做快照读,这种情况下不会对记录进行加锁。
不同事务隔离级别下,快照的创建判断
- Read Committed隔离级别: 因为RC级别下是允许读取到其他事务已经提交的数据的,所以在RC的同一个事务下每次select都会生成一个快照读;
- Read Repeatable隔离级别: 因为RR级别下是不允许读取到其他事务,所以在开启事务后第一个select语句是快照读的地方,后续的select也不会创建新的快照;
MVCC确保可重复读比较流程
在了解隐藏列和快照读之后,那么MVCC具体是怎么实现可重复读的处理的呢?在MVCC中有当有多个版本记录的时候,那么数据库应该给我们提供哪个版本可以查看的判断依据是什么呢?接下来我们看一下多版本中版本可见性的判断原理和比较流程。
可见性的判断
- 创建快照的这一刻,还未提交的事务是不可以被看到的; 在MVCC要创建快照读的这一刻,对于当前记录操作的其他事务如果还没有提交,那么我们肯定是不可以看到的。
- 创建快照之后创建的事务也不可以看到; 同样的在我们创建快照之后,创建的事务也不可以被看到。
实现方式Read View
什么是Read View?我们可以讲Read View理解为在事务开启的时候数据库当前所有活跃事务组成的事务列表,这个列表中都是当前数据库的活跃事务(注意,这里是活跃的事务列表,不一定包含是最老/最新的事务)。
- 快照读活跃的事务列表(创建快照的这一刻,将数据库中活跃的事务id提取出来组成活跃事务列表)
- 活跃事务列表中最小事务ID
- 活跃事务列表中最大事务ID
ReadView的比较逻辑
在创建快照这一刻,根据数据的事务id和活跃列表中的事务id进行比较来判断。
- 创建快照读的时候记录的事务id < ReadView中最小活跃事务,那么表示当前事务已经完成,可以直接查看事务对应的版本记录;
- 创建快照读的时候记录的事务id > ReadView中最小活跃事务,那么再继续和ReadView最大活跃事务id进行比较,如果大于最大活跃事务id,然后通过版回滚本指针,返回到上一个版本进行新的比较(创建快照时候的记录事务id > 最大活跃id表示,对记录操作的事务是在在快照创建之后开启的事务,因此需要找到创建快照时候的记录版本进行读取);
- 创建快照读的时候记录的事务id在ReadView活跃列表中,那么表示当前事务还在进行,然后通过版回滚本指针,返回到上一个版本进行新的比较;
- 创建快照读的时候记录的当务id在活跃列表的范围内,但是又小于活跃列表中最大的事务id(当前活跃最新的事务),那么可以看到对应的版本记录;
总结
MVCC通过快照读和隐藏列来实现多版本的控制,也通过了解Read View了解到MVCC对于多版本的快照进行比较的方式,这里记录下自己对于MVCC的理解,如果有不合理的地方欢迎留言指正和探讨。
在了解MVCC的原理有有一个点需要注意。
RR 与 RC 级别中 MVCC 的差异(核心是 Read View 生成时机)
MVCC 在 RR 和 RC 级别都能生效,但两者的 Read View 生成时机不同,导致行为差异:
- RR 级别:事务启动后,第一次执行 SELECT 时生成 Read View,且整个事务期间不更新。因此,事务内多次读取同一行,始终看到的是事务启动时的版本(解决不可重复读)。
- RC 级别:每次执行 SELECT 时都会重新生成 Read View。因此,事务内多次读取同一行,可能会看到其他已提交事务的最新修改(允许不可重复读)。