怎么解决幻读_对于MVCC以及如何在RR上解决幻读的理解

f310bffbb0bb2c6dc77fececfb95cdf6.png

关于四个隔离级别和并发事务存在的丢失修改、脏读、不可重复读等概念就不多赘述了

一、InnoDB如何解决幻读

  • 幻读:在InnoDB的可重复度隔离级别下,使用当前读,一个事务前后两次查询同一个范围,后一次查询会看到期间新插入的行;
  • 幻读的影响:会导致一个事务中先产生的锁,无法锁住后加入的行,会产生数据一致性问题;
  • 产生幻读的原因:行锁只能锁住一行,不能避免新插入的记录;
  • 解决幻读:在两行记录之间加上间隙锁,阻止新纪录的插入,与间隙锁产生冲突的只有“往这个间隙插入记录”这个操作;
  • 同时添加间隙锁与行锁称为Next-key lock,注意间隙锁只有在InnoDB的可重复度隔离级别下生效;
  • MVCC只实现读取已提交和可重复读,InnoDB在可重复度的隔离级别下,使用MVCC+Next-key lock解决幻读;

二、多版本并发控制MVCC

1.基本思想

  • MVCC是InnoDB实现隔离级别的一种方式,用于实现读取已提交和可重复读两种隔离级别;
  • 对于读取未提交,直接读取最新版本的数据;
  • 对于串行化,使用加锁的方式访问记录;
  • 大多数事务型存储引擎实现都不是简单的行锁,基于并发性的考虑,一般会同时实现多版本并发控制(MVCC)处理读写冲突;
  • MVCC是乐观锁的一种实现,是通过保存数据在某一个时间点的快照实现的,写操作更新最新的版本,读操作读取旧版本;
  • MVCC中事务的修改操作(增删改)会为行记录新增一个版本快照,并把当前事务id写入trx_id;

2.版本号

  • 系统版本号sys_id:每开始一个新的事务,系统版本号递增;
  • 在InnoDB中,聚簇索引记录中包含两个隐藏列:
    • trx_id:对记录进行改动时,trx_id会记录当前事务id,也就是当前系统版本号;
    • roll_pointer:对记录进行改动,会把旧版本记录写入undo日志,roll_pointer指向修改之前的版本;
  • 对同一条记录的更新,会把旧值放到一条undo日志中,作为一个旧版本的记录,多次更新之后这些版本会被roll_pointer连接成一个链表,称之为版本链

3.版本读取

  • 对于读取已提交可重复读,就会用到版本链,关键在于怎么判断版本链中哪个版本对当前事务可见;
  • 使用ReadView(快照),ReadView是一个包含当前已经开始但是没有提交的事务的列表,记录每个事务的事务id,记最小事务id为min_id,最大事务id为max_id;
  • 版本比较规则:
  1. 如果记录版本的trx_id小于min_id,说明这个记录版本是已经被提交过的,对其他事务可见;
  2. 如果记录版本的trx_id大于max_id,说明这个记录版本是ReadView生成之后发生的,不能访问;
  3. 如果记录版本的trx_id在min_id和max_id之间,判断trx_id是否在ReadView中:
  • 如果在ReadView中,说明事务还未提交,该记录版本不可访问;
  • 如果不在ReadView中,说明该事务已经提交,该记录版本可以访问;
如果当前记录版本不可读,就根据回滚指针roll_pointer找到旧版本的记录再进行判断;对于读取已提交,每次查询都会生成一个新的ReadView;对于可重复度,一个事务在第一次SELECT的时候生成一个ReadView,之后的查询复用这个ReadView;

4.快照读与当前读

  • 快照读:MVCC中的SELECT操作是读取快照中的数据,不需要进行加锁;
  • 当前读:MVCC中修改数据的操作(增删改)需要进行加锁操作,从而读取最新的数据;

5.例子

  • 假设当前有一个事务id为100的事务A,修改一个记录的name字段为name2,产生一个版本快照,因此有这样的版本链:

dd4cee69e273d94910c43ac62db72eb2.png
  • 假设事务A还没有提交,此时事务B进行SELECT,事务id为120,查询id为1的记录(记为第一次查询),此时生成ReadView为[100,120],根据版本读取规则,先找到trx_id为100的记录版本,发现不可读,于是通过回滚指针找到trx_id为60的记录,读取成功;
  • 当事务A提交之后,事务B再次进行SELECT查询id为1的记录(第二次查询),在读取已提交和可重复读两种隔离级别下有不同的情况:
    • 如果是读取已提交,则会创建一个新的ReadView为[120],此时读取trx_id为100的记录成功,也就是读取到了在事务期间提交的数据;
    • 如果是可重复读,则会使用第一次查询时的ReadView为[100,120],此时读取的是trx_id为60的记录,从而实现了可重复度;
菜鸡的小疑惑:
三级封锁协议不是可以解决脏读和不可重复度的问题吗,为什么要用MVCC来实现,是性能更好吗?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值