目录
MVCC多版本并发控制的原理
一、MVCC基本概念
一、MVCC简介:
MVCC(Multi-Version Concurrency Control),即多版本并发控制,在MySQL InnoDB中处理并发时,不加锁和非阻塞并发读,提高并发读性能的一种机制。MVCC维持了数据的多个版本,使得并发读写时没有冲突。
二、MVCC能够解决的问题:
读读:不存在并发问题
读写:存在并发问题,有事务隔离问题,脏读、不可重复读、幻读。
写写:有并发问题,可能会造成数据更新丢失。
三、MVCC目的
目的:就是多版本并发控制,在数据库中的实现,就是为了解决脏读和不可重复读等事务之间读写问题而诞生的,MVcc在某些场景中替代了相对的低效的锁(共享锁,排他锁),在保证了隔离性的基础上,提升了读取效率和并发性;
它的实现原理主要是依赖记录中的 3个隐式字段,undo log ,read view 来实现的。
后面三个隐含字段分别对应该行的隐含ID(DB_ROW_ID)、事务号(DB_TRX_ID,最新更新这条记录的事务ID)和回滚指针(DB_ROLL_PT,指向当前记录项的回滚的undo log记录 )
二、MVCC的核心原理
一、版本链:
每个数据行都有一个版本链,包含了该数据行的所有历史版本。每次更新操作(如INSERT、UPDATE、DELETE)都会生成一个新的数据版本,而不是覆盖旧版本。这些版本按照时间顺序连接在一起,形成版本链。
二、事务ID(Transaction ID):
每个事务都有一个唯一的事务ID,用于标识该事务的开始和结束。事务ID通常是递增的,并且是全局唯一的。
三、Read View(读视图)
当一个事务开始时,它会创建一个读视图(Read View)。读视图包括以下信息:
min_trx_id
:Read View 创建时的最小事务ID,表示该视图能够看到的最早版本。
max_trx_id
:Read View 创建时的最大事务ID,表示该视图能够看到的最新版本。
m_ids
:当前活跃的事务ID集合,表示哪些事务还没有提交。
三、MVCC实现原理
一、Mvcc具体是如何实现事务隔离的呢?
是基于undo-log版本链和readview来达成的,undo-log作为回滚的基础,在执行update和Delete操作时,会将每次操作详细记录在undo-log中,每条undo-log中都记录一个叫做roll_pointer的引用信息,通过roll_pointer就可以将某条对应的undo_log链接成一个undo链,在undo链的头部,通过数据行中的roll_pointer进行关联,就可以构成一条数据的版本链,这样对应每一行记录都会有一个版本链,体现了这条记录的所有变更,有事务对这条数据进行操作时,将操作后的数据链接到版本链头部;
二、SELECT操作:
在这里就需要使用readview结构来实现了,所谓readview顾名思义是一个视图内存结构,·在事务select查询数据时,就会构造一个readview,里面记录了该数据版本链的一些统计值,这样在后续查询处理是就无需遍历所有版本链了;
当一个事务执行 SELECT 操作时,它会使用自己的读视图来确定应该看到哪个数据版本。具体规则如下:
-
如果数据版本的事务ID小于
min_trx_id
,表示该版本已经提交,可以读取。 -
如果数据版本的事务ID大于
max_trx_id
,表示该版本在事务开始后创建,不可见。 -
如果数据版本的事务ID在
min_trx_id
和max_trx_id
之间,但在m_ids
集合中,表示该版本由尚未提交的事务创建,不可见。 -
如果数据版本的事务ID在
min_trx_id
和max_trx_id
之间,并且不在m_ids
集合中,表示该版本由已提交的事务创建,可见。
三、UPDATE操作:
-
创建新版本:当一个事务执行UPDATE操作时,它会创建一个新的数据版本,而不是直接修改原始数据。这个新版本会与当前事务的事务ID相关联,并记录在数据库中。
-
写入新数据:更新操作会写入新的数据版本,但原始数据版本仍然保留。这意味着其他正在执行SELECT操作的事务仍然可以看到原始数据版本,而不会受到UPDATE操作的影响。
-
提交事务:只有当事务成功提交后,新的数据版本才会对其他事务可见。在提交时,将更新的数据版本标记为已提交,而Read View会在其他事务中考虑这个已提交的版本。
四、MVcc可以解决不可重复读和幻读问题吗?
幻读是没有办法通过mvcc单独解决的,对应不可重复读问题,可以在事务第一个查询时,创建一个readView,后续查询都是用这一个readView进行判断,所以每次查询结果都是一样的就解决了不可重复读问题。
五、MVCC结合什么方式解决数据库幻读问题?
范围锁:
- 为了解决幻读问题,可以使用范围锁(Range Locking)。范围锁允许事务锁定一定范围的数据,以确保其他事务不能在这个范围内插入新数据或修改已有数据。这可以防止幻读问题的发生,因为事务可以锁定整个范围,从而保持了数据的一致性。
- 例如,在一个订单表中,如果一个事务正在检查某个时间范围内的所有订单,并且希望防止其他事务在这个范围内插入新订单,它可以使用范围锁来锁定这个时间范围。
五、补充点:
数据库隔离级别:
读未提交(read Uncommited):在该隔离级别,所有的事务都可以读取到别的事务中未提交的数据,会产生脏读问题,在项目中基本不怎么用,安全性太差;
读已提交(read commited):这是大多数数据库默认的隔离级别,但是不是MySQL的默认隔离级别;这个隔离级别满足了简单的隔离要求:一个事务只能看见已经提交事务所做的改变,所以会避免脏读问题; 由于一个事务可以看到别的事务已经提交的数据,于是随之而来产生了不可重复读和虚读等问题(下面详细介绍这种问题,结合问题来理解隔离级别的含义);
可重复读(Repeatable read):这是MySQL的默认隔离级别,它确保了一个事务中多个实例在并发读取数据的时候会读取到一样的数据;不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
可串行化(serializable):事物的最高级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争,一般为了提升程序的吞吐量不会采用这个;