一、MVCC原理
1.1 版本链
trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给 trx_id 隐藏列。
roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
假设之后两个 事务id 分别为 100 、 200 的事务对这条记录进行 UPDATE 操作,操作流程如下:
每次改动,都会记录一条 undo日志 ,每条 undo日志都有一个 roll_pointer 属性( INSERT 操作 对应的 undo日志 没有该属性,因为该记录并没有更早的版本),可以将这些 undo日志都连起来,串成一个链 表, 对该记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数的增多, 所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 版本链 ,版本链的头节点就是当 前记录最新的值。
二、ReadView
1、 对于读已提交和可重复读,需要使用上述的版本链,核心问题:找到哪个事务是正确的,可见的。那么怎么判断哪个事务版本是可用的呢,就需要ReadView了,我发现其实很多人都在说mvcc但是却不知道readView,真的是很奇怪。
2、 ReadView 主要由四部分组成:
1、m_ids:记录当前活跃的所有事务,相当于一个数组,[1,2,3,4]
2、min_trx_id: 记录当前活跃事务中最小的事务号。
3、max_trx_id:记录下一个应该开启的事务的事务号,事务号是递增的,其实就是时间戳。
4、creator_trx_id:表示生成该ReadView的事务号。
注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。
3、有了readView之后,再查询语句的时候就可以判断哪些事务的数据是可以被查到的了:
1、比如查询的数据的事务id < min_trx_id 表示数据在查询前没有操作过,可以操作。
2、如果数据的事务id == creator_trx_id 则表示当前事务在查询数据,可以操作。
3、如果数据的事务id > max_trx_id 则表示查询时,数据又被其他的事务操作过,不能查询数据,需要根据版本链查询。
4、如果min_trx_id < 数据的事务id <max_trx_id, 需要判断是否在m_id,并且不在m_id 列表中则代表,查询时已经被提交过,可以查询。
4、 对于读已提交和可重复读,在生成ReadView的规则不同,导致了他们读取数据时候的区别:
1、读已提交:每次执行select的时候都会生成一个readView,记录当前活跃的事务id。所以他可以读取到最新的别提交过的数据。
2、可重读度:他只会在第一次执行select 语句的时候生成ReadView,所以多次执行ReadView相同所以查询数据相同。
三、锁结构
当一个事务想对这条记录做改动时,内存中有没有与这条记录关联的 锁结构
trx信息 :代表这个锁结构是哪个事务生成的。
is_waiting :代表当前事务是否在等待。
如图所示,当事务 T1 改动了这条记录后,就生成了一个 锁结构 与该记录关联,因为之前没有别的事务 为这条记录加锁,所以 is_waiting 属性就是 false ,我们把这个场景就称之为获取锁成功,或者加锁 成功,然后就可以继续执行操作了。
在事务 T1 提交之前,另一个事务 T2 也想对该记录做改动,那么先去看看有没有 锁结构 与这条记录关 联,发现有一个 锁结构 与之关联后,然后也生成了一个 锁结构 与这条记录关联,不过 锁结构 的 is_waiting 属性值为 true ,表示当前事务需要等待,我们把这个场景就称之为获取锁失败,或者加锁失败,或者没有成功的获取到锁,画个图表示就是这样:
在事务 T1 提交之后,就会把该事务生成的 锁结构 释放掉,然后看看还有没有别的事务在等待获取锁, 发现了事务 T2 还在等待获取锁,所以把事务 T2 对应的锁结构的 is_waiting 属性设置为 false ,然后 把该事务对应的线程唤醒,让它继续执行,此时事务 T2 就算获取到锁了。如图:
不加锁 :意思就是不需要在内存中生成对应的 锁结构 ,可以直接执行操作。
获取锁成功,或者加锁成功:在内存中生成了对应的 锁结构 ,而且锁结构的 is_waiting 属性为 false ,也就是事务可以 继续执行操作。
获取锁失败,或者加锁失败,或者没有获取到锁:生成了对应的 锁结构 ,不过锁结构的 is_waiting 属性为 true ,也就是事务需要等 待,不可以继续执行操作。
在 READ UNCOMMITTED 隔离级别下, 脏读 、 不可重复读 、 幻读 都可能发生。
在 READ COMMITTED 隔离级别下, 不可重复读 、 幻读 可能发生, 脏读 不可以发生。
在 REPEATABLE READ 隔离级别下, 幻读 可能发生, 脏读 和 不可重复读 不可以发生。
在 SERIALIZABLE 隔离级别下,上述问题都不可以发生。