版本链

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(row_id 并不是必要的,我们创建的表中有主键或者非 NULL
的 UNIQUE 键时都不会包含 row_id 列):
trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务 id 赋值给 trx_id 隐藏列。
roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo 日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修
改前的信息。

每次对记录进行改动,都会记录一条 undo 日志,每条 undo 日志也都有一
个 roll_pointer 属性(INSERT 操作对应的 undo 日志没有该属性,因为该记录并没
有更早的版本),可以将这些 undo 日志都连起来,串成一个链表,所以现在的
情况就像下图一样:
image.png
版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务 id。于是可以利用这个记
录的版本链来控制并发事务访问相同记录的行为,那么这种机制就被称之为多版本并发控制(Mulit-Version Concurrency Control MVCC)

readview

image.png

对于RN级别 直接读最新版本数据
对于Serializable级别的,直接加锁来访问(表级共享锁)
对于RC和RR级别都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修
改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是:
READ COMMITTED 和 REPEATABLE READ 隔离级别在不可重复读和幻读上的区别是从哪里来的,其实结合前面的知识,这两种隔离级别关键是需要判断一下版本
链中的哪个版本是当前事务可见的。

readview:
m_ids:表示在生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
min_trx_id:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务 id,也就是 m_ids 中的最小值。
max_trx_id:表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
creator_trx_id:表示生成该 ReadView 的事务的事务 id。

如何判断当前版本可见:
1、如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,
意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
2、如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明
生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当
前事务访问。
3、如果被访问版本的 trx_id 属性值大于或等于 ReadView 中的 max_trx_id
值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不
可以被当前事务访问。
4、如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id
之间(min_trx_id < trx_id < max_trx_id),那就需要判断一下 trx_id 属性值是不是在
m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,
该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经
被提交,该版本可以被访问。
5、如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一
个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最
后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务
完全不可见,查询结果就不包含该记录。
总结:
事务id等于自己说明是我修改的,可见
小于最小的活跃事务,说明事务已提交,可见,大于最大的活跃事务说明是创建这个readview后的事务,当然不可见
在最大和最小活跃事务之间如果在不在当前活跃事务中说明已提交,可见,在当前活跃事务中说明未提交事务,不可见

在 MySQL 中,READ COMMITTED 和 REPEATABLE READ 隔离级别的的一个非
常大的区别就是它们生成 ReadView 的时机不同。
rc 每次读操作都会生成一个新的readview(因此能读到已经提交的事务修改的数据,导致不可重复读) rr第一次读的时候生成

mvcc基本解决幻读

幻读是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录,而这个记录来自另一
个事务添加的新记录。
示例:
在 REPEATABLE READ 隔离级别下的事务 T1 先根据某个搜索条件读取到多条记录,然后事务 T2 插入一条符合相应搜索条件的记录并提交,
然后事务 T1 再根据相同搜索条件执行查询。
根据版本链可见规则(RR只在第一次读时创建readview)
情况1.
T1先开启事务,第一次读后,T2后开启事务,对于T2插入的这条数据的版本链上事务id,对于T1生成的readview来讲T2trx_id大于等于max_id,不可见
T1先开启事务,T2后开启事务,T1.第一次读后T2插入 ,对于T1生成的readview来讲T2插入的这条数据的版本链上trx_id(t2)在t1的readview的最大最小之间且在活跃事务中,不可见
情况2.
T2先开启事务,T1后开启 对t1生成的readview来讲对于这条插入的数据的版本连上的trx_id(T2生成的)始终在t1的活跃事务列表中,不可见
因此基本上可以说解决了幻读.

但没有完全禁止的情况:
T1没有读到这条记录,但还是对这条记录做了update操作,这时候肯定update不会报错,这条新插入的记录版本链上最新的就变成了T1的事务id,当然就可见了