MVCC如何解决不可重复读和读取未提交

参考:《MySQL是怎样运行的:从根儿上理解MySQL》

目录

MVCC原理

版本链

ReadView

举例

读取已提交

可重复读

总结


MVCC原理

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

那么MVVC是怎么实现的呢,要理解其底层原理,需要知道两个概念,一个是版本链,表示某一个数据被修改的记录,比如某一条数据被事务1、事务2,、事务50依次修改。

一个是readview,表示在执行查询语句时会生成这样一个readview,里面有当前正在执行的事务有哪些,那么这些事务对应版本的数据我就不读取,这样不加锁我也知道哪些数据是还没有被提交的,顺着版本链我就知道哪些数据我可以读取,哪些不能读取。

详解如下

版本链

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:

trx_id : 每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给 trx_id
隐藏列。
roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息
每次对记录进行改动,都会记录一条 undo日志 ,每条 undo日志 也都有一个 roll_pointer 属性 ,可以将这些 undo日志 都连起来,串成一个链表,所以现在的情况就像下图一样:

 这个版本链中 有 80, 100 ,200 这三个事务,当然这三个事务80的可能已经提交结束了,200的和100的可能还没有,那么200和100 就属于活跃事务。

对该记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 版本链 版本链的头节点就是当前记录最新的值 。另外,每个版本中还包含生成该版本时对应的 事务id。

ReadView

ReadView 的概念,这个 ReadView 中主要包含 4 个比较重要的内容: 直接看下面的例子比较好理解
m_ids 表示在生成 ReadView时当前系统中活跃的读写事务的 事务id 列表。
min_trx_id:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的 事务id ,也就是 m_ids 中的最小值。
max_trx_id:表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
creator_trx_id 表示生成该 ReadView 的事务的 事务id
注意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。
只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会
为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

举例

假设一个表中,初始一个事务insert一条记录,名字是张一,事务提交,叫它事务A,事务id是1.

又来了事务2,修改名字为张二,但是还没有提交,属于活跃事务,

又来了事务3,修改名字为张三,也没有提交,属于活跃事务。

读取已提交

这时候又一个事务启动,执行了查询语句,因为只有查询,所以事务id为0,

那么首先会创建一个readview,里面有几项信息,比如当前活跃事务id表 ,这个表里面有2,3,

通过这个就知道2,3还没有提交,那么就不能读取这两个版本对应的名字,说不定后来它们又改成什么样子,而是按照版本链找,知道找到1,因为1不在活跃的事务id表中,所以读取这个版本对应的记录,读到“张一”。

如果之后事务2提交了,那么还是这个事务在读取的时候,就会新创建一个readview,这次里面的活跃id表中就没有事务2了,那么读到的就是“张二”。

可重复读

这时候又一个事务启动,执行了查询语句,因为只有查询,所以事务id为0,

那么首先会创建一个readview,里面有几项信息,比如当前活跃事务id表 ,这个表里面有2,3,

通过这个就知道2,3还没有提交,那么就不能读取这两个版本对应的名字,说不定后来它们又改成什么样子,而是按照版本链找,知道找到1,因为1不在活跃的事务id表中,所以读取这个版本对应的记录,读到“张一”。

如果之后事务2提交了,那么还是这个事务在读取的时候,不会新创建一个readview,这次里面的活跃id表中依旧是事务2,3,那么读到的依然是是“张一”,这样就实现了可重复读。

总结

READ COMMITTD 、REPEATABLE READ 这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了

有了这个 ReadView ,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:

如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。

如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。

如果被访问版本的 trx_id 属性值大于 ReadView 中的 max_trx_id值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。

如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

trigger333

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值