MySQL-MVCC:概述、工作原理、readView实现快照读、数据库解决问题、MVCC无法防止超卖

本文详细介绍了MySQL中的MVCC(多版本并发控制)机制,包括其工作原理、readView实现快照读、如何解决并发问题及隔离级别下的问题,同时也探讨了MVCC在防止超卖方面的局限性。
摘要由CSDN通过智能技术生成

MySQL-MVCC:概述、工作原理、readView实现快照读、数据库解决问题、MVCC无法防止超卖

一、概述

多版本并发控制(Multi-Version Concurrency Control,简称MVCC)是一种数据库管理系统中广泛采用的并发控制机制,尤其在MySQL的InnoDB存储引擎中得到了广泛应用。MVCC主要是为了实现事务处理的并发性,同时确保事务间的隔离性,从而避免脏读、不可重复读以及幻读等问题,而这些是ACID原则中的隔离性所要求的

二、MVCC工作原理

1、隐藏列维护版本信息

InnoDB为每行记录添加了额外的隐藏列来支持MVCC主要包括两个与事务相关的列

  • DB_TRX_ID(也称为创建版本号):记录的是最近修改该行数据的事务ID。
  • DB_ROLL_PTR(删除版本号或回滚指针):指向该行数据在回滚段(Undo Logs)中的undo记录。

2、快照读与当前读

  • 快照读(Snapshot Read):这是MVCC的核心概念非锁定读操作(如SELECT)默认情况下会看到一个历史版本的数据视图,即在事务开始时已经提交的所有数据版本
  • 当前读(Current Read):对某一行进行加锁的操作,如SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE,以及INSERT、UPDATE和DELETE操作,都会获取最新版本的数据,并可能阻塞其他事务对该行的访问

3、版本链与可见性判断

​ MVCC通过检查事务ID和系统版本号(每个事务都有一个系统版本号,可以理解为事务的“时间戳”)来决定某个事务能否看到某一行的版本。具体来说,当一个事务执行查询时,HeapTupleSatisfiesMVCC这样的函数会被调用来判断某一行对于当前事务是否可见

  • 如果DB_TRX_ID小于等于当前事务的系统版本号,那么这一行是可见的,因为它是早于当前事务开始前就已经提交的。
  • 如果DB_TRX_ID大于当前事务的系统版本号,但该事务ID对应的事务已提交,则检查undo日志来找到合适的可见版本。
  • 若事务ID对应未提交的事务或者找不到有效的undo日志,则该行不可见。

实际案例:
假设在MySQL的InnoDB存储引擎下,有两个并发的事务A和B,它们都处于可重复读(Repeatable Read)隔离级别

  • 事务A首先开始并执行了一个SELECT查询,此时它获得了一个数据快照。
  • 然后事务B开始,并更新了一行数据,提交了事务。
  • 此时,即使事务B已经改变了那行数据,事务A再次执行相同的SELECT查询时,由于MVCC的作用,它仍然会看到第一次查询时的数据版本,而不是事务B更新后的版本。

通过这种方式,MVCC实现了不同事务之间对同一行数据的不同版本的并发访问,增强了系统的并发性能,同时保证了事务的隔离性

三、readView实现快照读

​ MVCC(多版本并发控制)中的ReadView用来在并发环境下为事务提供一致性读视图的重要机制。MySQL的InnoDB存储引擎在可重复读(Repeatable Read)隔离级别下,通过ReadView来确保一个事务在其整个生命周期内看到的数据是一致的。
ReadView原理概述:
事务开始执行查询时,在可重复读隔离级别下会生成一个ReadView。这个ReadView包含以下关键信息:

  • creator_trx_id创建当前ReadView的事务ID

  • trx_ids生成ReadView时刻系统中活跃的所有读写事务的事务ID列表

  • up_limit_id活跃事务中最小的事务ID,通常是一个**“未提交阈值”**,小于这个ID的事务是已经提交完成的事务

  • low_limit_id下一个要分配给新事务的ID,即系统中最大的事务ID值,大于这个ID的事务一定是尚未启动或在生成ReadView后才启动的事务

判断某一行记录对于当前事务是否可见时,ReadView遵循如下规则

  • 如果行的DB_TRX_ID(事务ID)小于等于up_limit_id且不包含在trx_ids列表中,则该行对于当前事务是可见的,因为它是由之前已经提交的事务修改的
  • 如果行的DB_TRX_ID大于up_limit_id,则认为是不可见的,因为这是由后续创建的新事务修改的
  • 对于存在于trx_ids列表中的事务ID,如果与creator_trx_id相同,表示是当前事务自身对数据的修改,也是可见的;否则,由于这些事务在ReadView生成时尚未提交,所以该行对当前事务是不可见的

案例说明
假设在一个电商场景中,存在两个并发事务T1和T2,并且数据库设置的是可重复读隔离级别。

  • 事务T1首先开始并读取商品A的库存数量为10。
  • 同一时刻,事务T2也读取商品A的库存,并将库存减少到8,但此时还未提交。
  • 事务T1进入支付流程,需要再次确认库存。
  • 在此过程中,InnoDB为事务T1生成了一个ReadView,它包含了T2的事务ID,因为T2正在活跃但未提交。
  • 当事务T1再次尝试读取商品A的库存时,由于ReadView的作用,它不会看到T2尚未提交的更改,因此仍能看到库存为10,从而避免了因并发带来的数据不一致问题。

总结来说,ReadView通过维护不同事务间的版本关系确保每个事务看到的都是一个固定的历史版本集合,从而实现了在并发环境下的可重复读特性

四、MVCC 可以为数据库解决什么问题

​ MVCC(Multi-Version Concurrency Control,多版本并发控制)在数据库系统中主要用于解决以下问题:
1、并发访问冲突
​ 在高并发的数据库环境中,多个事务可能同时对同一数据行进行读写操作。如果没有合适的并发控制机制,可能会导致数据的一致性问题
MVCC通过为每个事务提供一个“快照”视图使得不同事务可以基于各自看到的不同版本的数据执行操作,从而减少因锁定造成的阻塞和等待

2、事务隔离级别下的异常现象

  • 脏读(Dirty Read):一个事务读取到了另一个未提交事务修改的数据。MVCC确保了事务只能读取到已经提交的数据版本,避免了脏读问题。

  • 不可重复读(Non-Repeatable Read):在一个事务内,两次执行相同的查询语句却得到不同的结果,因为在这两个查询之间,其他事务修改并提交了该数据。在可重复读(Repeatable Read)隔离级别下,MVCC通过保持事务开始时的数据快照来防止这种情况发生。

  • 幻读(Phantom Read):在一个事务中,同样的查询条件,在事务执行过程中由于其他事务插入新的行而导致前后查询结果集不一致。MVCC结合间隙锁(Next-Key Locks)在MySQL的InnoDB存储引擎RR隔离级别下有效解决了幻读问题。

实际案例:
案例一

假设在电商场景中,有两个并发的事务分别代表两个用户购买同一件商品的操作。

  • 事务A首先读取商品库存信息,然后进入支付流程但尚未完成提交。

  • 事务B在同一时间也读取了该商品库存,并成功完成购买和提交。

  • 如果没有MVCC,事务A继续完成支付后尝试扣减库存时可能会引发数据一致性问题;而有了MVCC,事务A只能看到自己开启事务时的商品库存状态,但是无法保证不会出现超卖情况

案例二

在银行账户转账场景中,用户A从自己的账户转账给用户B。

  • 假设事务1代表A查看自己的账户余额,此时事务还未开始转账操作。
  • 事务2是另一并发请求,将一笔资金存入A的账户并已提交。
  • 如果使用的是支持MVCC的数据库,当事务1开始执行转账操作时,它仍然会基于最初查看的账户余额来进行计算,而不是新到账的资金,这样就避免了不可重复读的问题,但是无法保证余额<0的情况

总结来说,MVCC通过对数据行创建多个版本,并根据事务的启动时间和可见性规则,有效地维护了事务间的隔离性,提高了系统的并发性能,并降低了死锁的发生概率

五、MVCC无法防止超卖

​ 即使在数据库中使用了MVCC(多版本并发控制),秒杀或者库存扣减等场景下仍然可能由于并发问题导致超卖现象。MVCC可以解决读操作的并发冲突,但在写操作密集且需要严格保证数据一致性的场景下,仅依靠MVCC机制并不足以防止超卖。
​ 在库存扣减的场景中,多个事务同时尝试扣减同一件商品的库存时,如果都基于同一初始库存值进行扣减,并最终提交事务,就有可能造成实际售出的商品数量超过库存总量,即出现超卖情况。
避免库存超卖,通常需要结合以下策略

  • 悲观锁:对要更新的库存记录加行级排他锁,确保在同一时刻只有一个事务能执行扣减库存的操作。

  • 乐观锁

    • 使用UPDATE ... WHERE inventory > 0 AND version = current_version的语句,其中version是一个用于乐观锁控制的字段,在每次更新前先检查该字段值是否与预期一致,若不一致则说明有其他事务已修改过库存,本次更新失败
    • InnoDB引擎中,虽然MVCC主要用于并发读取优化,但可以通过配合使用SELECT … FOR UPDATE来实现类似乐观锁的效果,锁定查询到的数据行,阻止其他事务在此期间对其进行修改。
  • 分布式锁:在分布式环境下,还可以通过Redis、ZooKeeper等中间件实现分布式锁,确保全局范围内库存扣减的原子性和一致性。

  • 队列处理:将所有的购买请求放入一个消息队列,然后由后台服务按照顺序逐个处理扣减库存,这样也可以避免并发带来的超卖问题。

  • 预扣减库存:在用户点击购买按钮时,直接根据预计的订单量一次性扣除库存,然后再进入支付环节,一旦支付失败再恢复库存,这种做法简单粗暴,但可能会牺牲一定的用户体验和系统复杂度。

综上所述,虽然MVCC有助于提高并发读取性能并降低死锁,但它并不能完全解决高并发场景下的库存超卖问题,还需要结合其他的并发控制策略来确保库存扣减的正确性

MySQL-MVCC:概述、工作原理、readView实现快照读、数据库解决问题、MVCC无法防止超卖 到此完结,笔者归纳、创作不易,大佬们给个3连再起飞吧

  • 35
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小孔靠得住

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

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

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

打赏作者

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

抵扣说明:

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

余额充值