(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨
👀👀👀 个人博客:小奥的博客
👍👍👍:个人CSDN
⭐️⭐️⭐️:Github传送门
🍹 本人24应届生一枚,技术和水平有限,如果文章中有不正确的内容,欢迎多多指正!
📜 欢迎点赞收藏关注哟! ❤️
文章目录
如何实现可重复读
今天来带大家学习一下,MVCC是如何实现可重复读的。
假设事务A和事务B同时堆主键id = 1的记录进行操作,事务A和B的事务id分别为20和30 。
那么这两个事务就会创建各自的ReadView:
此时事务A的creator_trx_id = 20
,事务B的creator_trx_id = 30
。由于仅有两个活跃的事务,所以事务列表中最小的事务是事务A,所以min_trx_id = 20
,下一个也就是最大的事务id的max_trx_id
值应该为事务B的下一个id,即max_trx_id = 31
。
事务A去读取主键id为1的数据,找到记录之后就会查看该记录的trx_id
,假设事务A查询到该记录的trx_id
为10 。
随后和自己的creator_trx_id
进行比较:
发现主键id = 1的记录 trx_id = 10 < A.creator_trx_id = 20
,就判断到该记录的事务id不存在于活跃的事务列表中小于自己的事务id,这代表本次记录的值是在自己查询之前提交的,所以可以读取,并且在读取完之后,将该记录的trx_id修改为自己的事务id。
然后,把age字段的值从28修改为30 。
另外,被修改的字段还有Undo Log中的另一个隐藏字段:roll_pointer
指针。它会去指向被事务A修改的之前的数据版本,也就是fancy的年龄为28的数据的地址,就是为了用来记录,方便下次被查询。
随后,事务B也对该条数据进行操作,事务B的要求是将fancy的年龄从30直接修改为50 。
此时会再次进行一次trx_id
的比较过程,去判断自己的creator_trx_id
是否大于这条记录对应的trx_id
,如果大于,就去修改这条记录的值,将年龄从30修改为50:
并且Undo Log版本链也会更新对应的数据。
重点来了
如果此时事务A再去读取主键id = 1这条记录,发现这条记录的trx_id已经被修改为了30,再次进行事务id之间的区间比较:发现 A.trx_id = 20
< 主键 id = 1 记录.trx _id = 30
< max_trx_id = 31
,并且trx_id = 30
存在于活跃事务集合m_ids中,就代表自己读取到的是和自己同一时间范围内一块启动的另一个未提交的活跃事务所修改的值。
那么此时事务A是不会去读取这条记录对应的数据的,它会通过Undo Log版本链上的roll_pointer
指向的地址去查找上一个旧版本的记录,直到找到第一条trx_id小于等于自己的事务id并且不存在于活跃事务id集合m_ids列表中的记录,代表是别的事务已经提交的最后一条记录然后读取它。
这样的话,每一个事务去读取或者修改同一个记录时,只能操作已经提交的数据,未提交的数据时不能被读取到的,MySQL就这样实现了可重复读。
其实本质上就是通过Read View的字段判断这行记录是否对自己可见,如果不可见的话再去找Undo Log里面的记录,直到找到对自己可见的数据,然后才能进行操作。
如何实现提交读
提交读能够解决脏读,脏读问题其实本质上就是一个事务读取到了另一个事务没有提交的内容。
下面我们来学习一下MVCC是如何实现提交读的。
假设事务A和事务B同一时刻启动,事务B将同一行的记录,也就是fancy的年龄改成了25,但是事务B并没有提交,此时事务A就会去读取这条记录的trx_id
。
事务A查看到该记录的trx_id
居然比事务A的Read View列表中的creator_trx_id
值大,并且修改这条记录的事务trx_id
存在于自己的m_ids
列表里面,那么事务A就可以判断到该记录是被另一条没有提交的事务修改的,所以它不会去读取这条数据的内容 ,事务A会通过Undo Log版本链继续找第一条trx_id
小于等于自己的事务id并且不在m_ids
列表里面的数据。
所以,事务A不会看到事务B正在修改的数据,脏数据也不会产生。
总结
InnoDB 中,MVCC 就是通过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮我们判断当前版本的数据是否可见。从而不需要通过加锁的方式,就可以实现提交读和可重复读这两种隔离级别。
总的来说,MVCC本质上就是一种数据结构。已提交读和可重复读都是使用了Read View这种策略通过区间判断获取自己能够读取的内容,然后展示。InnoDB通过MVCC,解决了脏读、不可重复读。