MVCC与不可重复读、幻读

1 可重复读的意义

        众所周知,事务隔离级别分为读未提交、读已提交、可重复读、串行化,解决了提交丢失、脏读、不可重复读、幻读一系列问题。但心中一直有个疑问,为何要出一个可重复读隔离级别来解决不可重复读问题?读已提交所提供的“其他事务可读取到已提交的事务”,其实是一种比较合理的状态,既然成功提交了就给他读呗。那可重复读存在的意义是什么?

        查阅资料外加一阵思考后,大概有点头绪了。我理解是为了维持一个相对稳定的状态,MySQL是一个关系型数据库,且具有极为重要的事务ACID四大特性。结合这几点来看,MySQL意图提供给用户稳定而完整的服务。

        假设这样一个场景:

商户在进行今日货物清点,在商品信息表查询商品剩余数量、在订单信息表查询今日总交易量、在交易流水表查询今日总交易金额。在盘点期间商户店铺不冻结,可以正常进行交易操作。

         使用读已提交级别:

  1. 事务开启后,查询商品剩余数量10件,商户算了算得出今日售出40件;第一步完成后,有顾客又来下了一单,那么此时商品剩余数量为9件、今日售出41件,但是第一步已经查剩余商品10件,这件事我们先按下不表。
  2. 第二步查询总交易量,此时由于刚刚顾客下了一单,总交易量已经变成了41件,这一步就已经和商户想得不太一样了。
  3. 到了最后一步,总交易额查询结果也是售出41件的交易额。

        那么最后商户拿到的结果是:今日剩余10件商品,售出41件商品,卖出41件商品的金额。商户懵逼了,百思不得其解为何会这样,难道是上帝嘉奖了自己的勤奋,多给了一份商品的钱?但是商户刷新后,发现一切都是一场空,钱数原来是正确的,馅饼又飞回了天上。假设极端情况下,商户每次刷新之后的查询期间,都有一位顾客来下一单,那这种数据不一致的延迟就会维持到商户离开人世。

        讲到这里,可重复读的意义出现了,上述例子的情况显然是一种非稳定状态,每次顾客的操作都会导致数据库记录的不稳定,这与MySQL的理念(我想象中的)是相悖的。可重复读完美地解决了上述场景的问题,在事务开启时针对涉及的表记录创建快照,此后所有读取到的记录都维持了同一时间下相对稳定的状态。

        那在可重复读情况下,我们还有可能读到其他事务提交的记录吗?答案是有可能,更具体地说是有可能读到其他事务新增的记录,但不可能读到被其他事务修改后的记录;快照读下数据是定格的,完美解决了不可重复读问题,而使用当前读则能读到其他事务新增的记录,这就是另一个问题“幻读”。

2 幻读

       在此之前先介绍一下MySQL的MVCC(Multi Version Concurrency Control,多版本并发控制)机制,熟悉这个的朋友们应该也对“DB_TRX_ID”(全局事务号)比较了解,MySQL利用全局事务号 + 当前版本号 + 删除版本号实现了快照读/当前读两种读取方式。大概意思就是,我们看到的一行数据,其实是由多行不同版本的数据组成的。让我们利用下面的例子,来关注id为1的记录版本号变化。

操作SQL语句当前版本号删除版本号版本号变更记录
事务1 插入记录

INSERT INTO table (id, value) VALUES (1, 1)

1事务1插入记录,当前版本号为全局事务号1;记录未被删除或更改,因此无删除版本号。
事务2 查询记录SELECT * FROM table WHERE id = 11事务2查询记录,查询当前版本号 ≤ 全局事务号、删除版本号不存在或 > 全局事务号对应记录。这是为了查到当前事务下,能看到的最新记录。
事务3 修改记录UPDATE table SET value = 2 WHERE id = 13事务3修改记录,当前事务号为全局事务号3。
INSERT INTO table (id, value) VALUES (1, 1)13隐藏行,为id = 1记录于事务1插入的历史版本,于事务3被删除过期,因此删除版本号为当前事务号3。
事务4 查询记录SELECT * FROM table WHERE id = 13事务4查询记录,全局事务号4 > 删除版本号3、全局事务号4 > 当前本版本号3,因此查询到value = 2的记录。
事务5 删除记录DELETE FROM table WHERE id = 135事务5删除记录,将删除版本号设置为全局事务号5。
事务6 查询记录SELECT * FROM table WHERE id = 135事务6查询记录,发现3个版本记录的删除记录号都小于全局事务号,所以搜索不到任何记录。

        看完上面的流程,相信大家对MVCC已经有了基本认知。就是根据同一条记录的不同版本号,与开启事务时的全局事务号进行对比,得到能够读取的记录;类似于8点开始读取,别人9点进行的修改不可见,但7点50的修改可见。

        再回到之前说的有可能读到其他事务提交的记录,其实就是基于快照读(普通SELECT)当前读(排他锁FOR UPDATE、共享锁SHARE MODE)产生的差异,快照读是将时间定格在8点,对数据库状态拍张照拿去慢慢读,这种情况下是不可能读到其他事务提交的记录的(不论是修改、删除、插入),也就解决了幻读问题;而当前读顾名思义,永远将最新时间作为起点,读取最新时间下的最新修改。

        看到这里可能有朋友就懵逼了,照这么说当前读也会造成不可重复读,永远读最新的记录,别人修改并提交你就读到最新的结果——但是不要忽略一点哈,排他锁会给查询的记录加上锁,其他事务对于加锁记录的修改操作会被阻塞,还会加上间隙锁(Next key + Gap),因此说完美地解决了不可重复读问题。

        排他锁会阻塞修改操作,但无法阻塞插入操作,因为排他锁只会锁住id对应记录和周围几条,插入和这个范围内的记录并没有什么联系,除非锁表,但是没必要。

        因此当前读并不能解决幻读问题,而快照读能够解决幻读问题,但他们都解决了不可重复读问题。

MVCC(多版本并发控制)是一种用于解决并发读写冲突的机制。在MVCC中,数据库会为每个事务创建一个快照,该快照是事务开始时数据库中数据的一个副本。因此,在可重复读隔离级别下,MVCC通过使用快照读来解决可重复读的问题。 在可重复读中,多个事务同时读取同一行数据时,MVCC会根据每个事务开始时的快照来提供一致性视图。这意味着,如果一个事务正在读取数据,并且另一个事务正在修改相同的数据,那么读取操作将返回事务开始时的数据快照,而不会受到其他事务的修改的影响。 具体来说,MVCC通过在每个数据行中保存多个版本的数据来实现可重复读。当一个事务开始时,它会创建一个读取快照,并且只能看到在该快照之前已经提交的数据版本。如果其他事务正在修改相同的数据行,那么这些修改将被保存在新的数据版本中,而不影响当前事务的读取操作。 因此,MVCC通过使用快照读和当前读的组合来实现可重复读。快照读提供了一致性的数据视图,而当前读则用于读取最新的数据,并在写操作之前读取数据。 总之,MVCC通过使用快照读和多个数据版本来解决可重复读的问题,确保事务在读取数据时不会受到其他并发事务的修改的干扰。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [MySQL(五)—MVCC解决可重复读幻读了吗?](https://blog.csdn.net/shang_0122/article/details/120298810)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [详解MySQL是如何解决幻读的](https://download.csdn.net/download/weixin_38730767/13699659)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值