0、前言
本节要求的基础是,学习了数据库的几大读写问题和隔离级别后(如脏读、不可重复读、幻读;读未提交、读已提交、可重复读和串行序列化)、数据库的锁(读锁、写锁等)、并且在学习了UNDO日志的基本知识后,本文将介绍一项技术用于替代锁的控制。
1、总体概括:什么是MVCC多版本控制
加锁,是一种悲观锁、非阻塞并发读,为了提高数据库的并发性能,更好地处理读写重读,需要引入MVCC多版本控制技术来替代锁。
而MVCC技术主要解决的问题就是:读已提交、可重复读,这两种隔离级别的底层是如何实现的呢——通过MVCC实现!
其次,“多版本控制”,是通过数据行的多个版本管理,来实现并发控制。
例如,你想进行可重复读,那要读的就不该是另外一个已提交事务修改后的数据,而是最初版本的数据。我们可以通过多版本控制实现这类的操作,从而完成读已提交、可重复读。
2、基础知识
2.1 数据的隐藏字段
undo日志的版本链:对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。
- trx_id:系统在每次一个事务对某条聚簇索引记录进行改动update时,都会把该事务的 事务id 赋值给 trx_id 隐藏列(这个id是递增的),如果为查询操作,id默认为0。
- roll_pointer:每次对某条聚簇索引记录进行改动update时,都会把旧的版本写入到 undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
例如图下所示,对记录进行反复的改动,就会形成链表,我们把这个链表称之为 版本链
2.2 ReadView(每个事务开始时都会构建一个它)
ReadView是实现MVCC(多版本并发控制)机制的核心组件。
ReadView是在执行可重复读和读已提交隔离级别事务时创建的一个数据结构,用于识别出所读取行的可见性。在每个事务开始时,MySQL会将当前系统中所有活跃事务的ID号和其启动时创建的快照信息构建成一个ReadView。这个ReadView中包含了以下几个主要的元素:
3、ReadView规则
在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次 Read View。
当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会 获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View下。
有些晦涩,直接通过例子进行讲述:
4、MVCC流程示例
4.1 例1:读已提交
(1)构建两个事务10、20(系统自动分配事务id),都没提交:
(2)用可重复读,去查询,理论上应该查出张三:后面要通过MVCC解释为什么是张三:
分析:
当前是读的操作,因此生成ReadView的creator_trx_id为0,活跃事务列表trx_id为[10,20],up为10,low为21.
然后去依次查版本链表,王五的trx_id是10,在[10,20]里,因此还是活跃的,不能读。李四同理,等到张三trx_id是8 < up,因此把张三读出来。
(3)提交事务id为10的事务再更新事务20,链表变为如下:
此时再查一遍:
分析:
每个事务中每次select都要生成ReadView(Read committed情况下),此时ReadView里的trx_id为[20],依次读undo日志,直到王五的trx_id为10<20,则查到王五。
4.2 例2:可重复读
使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之后的查询就不会重复生成了。
分析:
前面步骤与可重复读一样,区别在于,提交以后,现在要可重复而不是读提交的:因为ReadView一直是最初的样子,即
ReadView的creator_trx_id为0,活跃事务列表trx_id为[10,20],up为10,low为21.
因此,一直找到trx_id==8 < 10的张三,才查找出来结果。
5、总结
无论事务是否提交,都会在undo日志中留下链表记录。区别在于,提交与否会改变ReadView里的活跃事务列表trx_id,这是判断版本号是否合规的关键。
但是由于,不同的事务隔离级别而言,读已提交才对trx_id的变化敏感,而可重复读的情况下,只读一次ReadView,因此它怎么变化也就无所谓了。