数据库的两种读,每种读读的数据版本不一样(快照读和当前读),所以也称为MVCC,即多版本并发控制
版本链
又称事务链,每次修改数据的时候,都会记录一条undoLog日志,日志中记录数据每一次的变化,并且通过数据中的两个隐藏列
db_trx_id(事务id) 用标记每一条数据对应的事务id。
db_roll_ptr(指针) 用来串联版本链的指针。
db_row_id:隐藏主键,如果我们没有给这个表创建主键,那么会以这个字段来创建聚簇索引。
因为每一次记录变更之前都会先存储一份快照到undo log中,那么这几个隐式字段也会跟着记录一起保存在undo log中,就这样,每一个快照中都有有一个db_trx_id字段记录了本次变更的事务ID,以及一个db_roll_ptr字段指向了上一个快照的地址。(db_trx_id和db_roll_ptr是重点,后面还会用到)
这样,就形成了一个快照链表:
有了undo log,又有了几个隐式字段,我们好像还是不知道具体应该读取哪个快照,那怎么办呢
这时候就需要Read View 登场了
ReadView
阅读视图,可以理解为某一次读取的时候,根据不同的事务生成的数据快照,并且除了表中的数据,还有一下这些固定字段。
-
trx_ids,系统当前未提交的事务 ID 的列表。
-
low_limit_id,应该分配给下一个事务的id 值。
-
up_limit_id,未提交的事务中最小的事务 ID。
-
creator_trx_id,创建这个 Read View 的事务 ID。
每开启一个事务,我们都会从数据库中获得一个事务 ID,这个事务 ID 是自增长的,通过 ID 大小,我们就可以判断事务的时间顺序。
那么,一个事务应该看到哪些快照,不应该看到哪些快照该如何判断呢? 关键点来了
其实原则比较简单,那就是事务ID大的事务应该能看到事务ID小的事务的变更结果,反之则不能!
假如当前有一个事务3想要对某条记录进行一次快照读的时候,他会先创建一个Read View,并且把当前所有还未提交的事务
的信息记录下来。比如up_limit_id = 2,low_limit_id = 5,trx_ids= [2,4,5],creator_trx_id= 6(这里都是未提交的事务,牢记)
ReadView生成规则
-
READ COMMITTED(读已提交): 在一个事务中,每次查询都会生成一个新的ReadView
-
REPEATABLE READ(可重复读) :在一个事务中只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了
ReadView校验规则
- db_trx_id:对这条记录做了最新一次修改的事务的ID
- low_limit_id:应该分配给下一个事务的id 值。
- up_limit_id:未提交的事务中最小的事务 ID。
-
如果db_trx_id与ReadView中的creator_trx_id值相同,说明当前事务修改的记录就是在当前事务下操作的,那当然是对我们可见的了,因此可以修改这条记录
-
如果db_trx_id小于ReadView中up_limit_id值,说明(生成该版本的事务在该事务生成readView之间已经提交)即当前事务在开启的时候,这条记录最近一次被其他事务操作的事务已经提交了,所以对这条记录对我们来说也是可以见,可以修改
-
如果db_trx_id大于或者等于ReadView中low_limit_id值,说明(生成该版本的事务在当前事务生成readView之后才开启)即:我们开启事务未修改该记录之前,已经有另外一个事务开启,并且正在修改该事务了,因此,这条记录对我们来说依然是不可见的,我们不能修改
-
如果db_trx_id介于ReadView中 up_limit_id 和 low_limit_id 之间的话
有两种情况,分析当db_trx_id是否在 trx_ids 中
① 如果在,说明(创建ReadView时,生成该版本的事务还处于活跃状态)即:当前已经有其它的事务正在修改该条记录,并且还未提交,此时这条记录对我们不可见
② 如果不在,说明(创建ReadView时,生成该版本的事务已经提交)即:此时没有事务操作该条记录,我们可以修改该条记录
当数据的事务ID不符合Read View规则时候,也就是不可见的时候,那就需要从undo log里面通过版本链的回滚指针找到这条记录的上一个版本在进行判断,直到遍历版本链找到对当前查询可见的版本返回,找不到则返回空。