我们知道,在Mysql的RC隔离级别下和RR隔离级别下MVCC才会生效,它的实现原理是靠undo log版本连以及Read View来实现的,下面我们来分析一下。
快照读快照读很简单,其实就是普通的select,是一种非阻塞无锁操作。
当前读读的是当前最新的数据,比如 select ···· for update,insert,update等都是当前读,是要加锁的。
隐藏字段在每一行记录的后面其实会有两个隐藏字段
DB_TRX_ID:保存在该行记录上执行insert语句或者update语句的当前事务ID。
DB_ROLL_PTR:回滚指针,记录着该记录的上一个版本,相当于链表的next指针。
Read View是进行快照读的依据,是一种数据结构,它由四个部分组成
- m_ids:当前活跃的所有事务ID
- min_trx_id:最小活跃事务ID
- max_trx_id:下一个事务ID,最大活跃事务ID + 1
- creator_trx_id:创建该Read View的事务ID
对每一个事务,只能读到小于等于DB_TRX_ID(行记录的隐藏字段)的版本的数据,比如说现在的事务ID是4,它只能读到1到4版本的数据,那么该读哪个版本呢?要遵循以下逻辑:
(注意:以下的当前事务ID是指版本链中记录的每一个已操作的事务ID)
- 判断当前事务ID是否等于creator_trx_id,如果相等,表示是自己修改了自己,当然可以访问,如果不相等,进入下一轮判断。
- 判断当前事务ID是否小于min_trx_id,如果小于,表示事务已经提交,当然可以访问,如果不相等,进入下一轮判断。
- 判断当前事务ID是否大于max_trx_id,如果大于,则表示该事务实在ReadView生成以后再开启的,不允许访问,如果小于,则进入下一轮判断。
- 判断当前事务ID是否 min_trx_id <= DB_TRX_ID <= max_trx_id ,如果成立,则看看该事务ID是否在m_ids中,如果不存在,则表明事务已提交,可以读取。
而RC隔离级别和RR隔离级别的区别就在于产生Read View的时机。
RC隔离级别下的情况在RC隔离级别下,在每次进行快照读的时候,都会生成Read View,因此每次快照读都能知道是哪些数据没有提交,再经过上述判断后,每次快照读都会读到已提交的数据,因此才会出现不可重复读的现象。
如图
InnoDB是以每一个版本链的DB_TRX_ID去比较的,一个不行就下一个直到拿到数据为止,整个过程是这样的:先从DB_TRX_ID = 2开始,发现2不等于creator_trx_id,因此下一轮循环,然后再发现2不小于min_trx_id,进入下一轮循环,发现2小于max_trx_id,进入下一轮循环,发现2是在m_ids里的,不符合,因此根据回滚指针,指向下一个版本即DB_TRX_ID = 1,再次经过上述逻辑,发现符合,因此取出DB_TRX_ID = 1的这条数据,因此第一次快照读读的是name = B,而同理,第二次快照读读的是name = C。
RR隔离级别下的情况和RC隔离级别几乎完全相同,唯一不一样的地方就是,RR级别只会生成一次Read View,之后的每次快照读都会根据这个Read View 来读,但是要注意一点,如果有当前读发生,则会清除Read View并且重新生成一个新的Read View,因此MVVC只是可以解决部分的幻读现象。



