在我们的数据库隔离级别中幻读的解决是在串行化的级别下进行处理的,虽然这种方法可以解决幻读,但是这种方法在高并发下效率是非常低的,经过学习了解到两种解决幻读的方法:加间隙锁和MVCC。

加间隙锁

间隙锁:将数据分为不同区间,对该区间进行加锁。作用在索引上,其目的是为了防止同一事物的两次当前读出现幻读的情况。如果对一条记录添加了间隙锁,并不会影响其他事务对这条记录加记录锁或者继续加gap锁。
比如说我们有1,3,5,10,12这几条数据:
![在这里插入图片描述](https://img-blog.csdnimg.cn/b5afde0fd66940bd82f0cc182f377e64.png
在当前事务对5-9中的数据进行一次查询并添加间隙锁后,另一个事务想要在5-9这个范围中添加数据是会阻塞的或者说添加失败。也就是说使用间隙锁后,其他事务就不能在加锁的范围中添加数据,这样就可以防止幻读的产生
在这里插入图片描述
1、session A 先对9进行查询并在(5-10)范围添加间隙锁,
2、session B 也对9进行查询并在(5 - 10)范围添加间隙锁
3、此时session B想插入9这条记录,由于session A对(5 - 10)进行了加锁,所以不会插入成功
4、同时seesion A也想插入9这条记录,由于session B对(5 - 10)也进行了加锁,插入操作不会成功,此时B在等待A会话完成,A在等待B会话完成,所以出现了死锁现象

MVCC

MVCC也就是多版本并发控制,不需要通过加锁手段就能读到正确版本的数据,其存在目的是在保证数据一致性的前提下提供一种高并发的访问性能。
换言之,就是为了查询一些正在被另一个事务更新的行,并且获取到更新前的值,这样在做查询时就不用等待另一个事务释放锁。

MVCC实现原理:隐藏字段,undo log,ReadView

隐藏字段:在InnoDB存储引擎中,对于每一条记录都会有隐藏字段,包括ROWID,事务ID(最新一次被哪个事务修改),回滚指针

ReadView: 当前数据的快照

  • READ COMMITTED隔离级别下,一个事务执行过程中每次执行SELECT操作都会生成一个ReadView,ReadView本身就保证了事务不可以读取到未提交的事务做出的修改,也就避免了脏读现象
  • REPETABLE READ隔离级别下,一个事务执行过程中只有第一次执行SELECT操作时才会生成一个ReadView,之后的SELECT操作都是复用这个ReadView,这也就避免了不可重复度和幻读

ReadView规则

ReadView到底是怎么避免幻读的,这就需要知道ReadView都包括什么:

ReadView中包括有:生成ReadView的当前事务,生成ReadVIew时活跃(尚未提交)的事务列表(事务ID从小到大进行排列),以及列表中的最小事务ID(up_limit_id),以及下一个尚未分配过的事务ID(low_limit_id)

详细规则:

  1. 如果所查询的数据的隐藏字段中的事务ID就是当前ReadView中的事务ID,则表示当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问到

  2. 如果所查询的数据的隐藏字段中的事务ID小于当前ReadView中的up_limit_id,则表示所查询数据是被修改并已经提交的,所以该版本可以被当前事务访问到

  3. 如果所查询的数据的隐藏字段中的事务ID大于当前ReadView中的low_limit_id,则表示所查询的数据是在当前事务之后被修改过的,所以不能被当前事务访问到

  4. 如果所查询的数据的隐藏字段中的事务ID在up_limit_idlow_limit_id范围之内:

    • 如果所查询的数据的隐藏字段中的事务ID和范围中的某个事务ID相同,则表示修改了数据的事务还是活跃的,没有提交,所以不能被当前事务访问
    • 如果所查询的数据的隐藏字段中的事务ID和范围中的某个事务ID没有相同的,则表示事务已经提交,所以可以被当前事务访问到

MVCC执行流程

  1. 开启事务时会有一个事务ID(单纯的查询事务ID为0)
  2. 获取ReadView
  3. 查询得到的数据与ReadView中事务版本号进行比较
  4. 如果不符合ReadView规则,则要从undo log中获取历史快照
  5. 最后返回符合规则的数据
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐