目录
前言
大家好,我是月夜枫~~
相信所有用过MYSQL的小伙伴都听过MVCC版本链,那么MVCC究竟是什么呢?又起到了什么样的作用?以及为什么都会选择使用MVCC?今天带大家盘点一下MVCC的相关知识点,和大家一起学习一起进步。废话不多说上连接。。。。。
一、什么是MVCC?
MVCC是多版本并发控制(Multi-Version Concurrency Control)的简称,它是一种数据库并发控制的方法,旨在实现对数据库的并发访问。MVCC通过维持数据的多个版本,使得读写操作在没有冲突的情况下进行。具体来说,当对数据库中的某个数据元素进行写操作时,会为该元素创建一个新版本;而进行读操作时,MVCC会选择一个适合当前事务的数据版本供读取。这种方式消除了读和写操作的冲突,从而提高了数据库的性能和并发性。
MVCC的实现依赖于数据库系统中的Undo log和版本链,这些机制使得不同事务可以并发执行,而无需使用锁。在MySQL的InnoDB存储引擎中,MVCC主要用于提高数据库并发性能,特别是在使用读已提交和可重复读隔离级别的事务中。通过这种方式,即使存在读写冲突,也能实现非阻塞的并发读取,从而提升了系统的整体性能。
多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。在内部实现中,与Postgres在数据行上实现多版本不同,InnoDB是在undolog中实现的,通过undolog可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在InnoDB内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。
MVCC是一种多版本并发控制机制,在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。
总的来说,MVCC是一种重要的数据库并发控制机制,它通过维护数据的多个版本,使得多个事务可以同时访问数据库,而不会相互干扰,这对于提高数据库系统的性能和响应速度至关重要。
你可将MVCC看成行级别锁的一种妥协,它在许多情况下避免了使用锁,同时可以提供更小的开销。根据实现的不同,它可以允许非阻塞式读,在写操作进行时只锁定必要的记录。
MVCC会保存某个时间点上的数据快照。这意味着事务可以看到一个一致的数据视图,不管他们需要跑多久。这同时也意味着不同的事务在同一个时间点看到的同一个表的数据可能是不同的。如果你从来没有过这种体验的话,可能理解起来比较抽象,但是随着慢慢地熟悉这种理解将会很容易。
各个存储引擎对于MVCC的实现各不相同。这些不同中的一些包括乐观和悲观并发控制。我们将通过一个简化的InnoDB版本的行为来展示MVCC工作的一个侧面。
InnoDB:通过为每一行记录添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。让我们来看看当隔离级别是REPEATABLE READ时这种策略是如何应用到特定的操作的:
SELECT InnoDB必须每行数据来保证它符合两个条件:
1、InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。
2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候。
符合这两个条件的行可能会被当作查询结果而返回。
INSERT:InnoDB为这个新行记录当前的系统版本号。
DELETE:InnoDB将当前的系统版本号设置为这一行的删除ID。
UPDATE:InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。
这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。
MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据 [1]。
通过使用MVCC(Multi-Version Concurrency Control)算法自动提供并发控制。MVCC维持一个数据的多个版本使读写操作没有冲突。也就是说数据元素X上的每一个写操作产生X的一个新版本,GBase 8m为X的每一个读操作选择一个版本。由于消除了数据库中数据元素读和写操作的冲突,GBase 8m得到优化,具有更好的性能。特别是对于数据库读和写两种方法,他们不用等待其他同时进行的相同数据写和读的完成。在并发事务中,数据库写只等待正在对同一行数据进行更新的写,这是现有的行级锁的弱点。同时MVCC回收不需要的和长时间不用的内存,防止内存空间的浪费。MVCC优化了数据库并发系统,使系统在有大量并发用户时得到最高的性能,并且可以不用关闭服务器就直接进行热备份。
MVCC机制具有以下优点:
- 提高并发性能:读操作不会阻塞写操作,写操作也不会阻塞读操作,有效地提高数据库的并发性能。
- 降低死锁风险:由于无需使用显式锁来进行并发控制,MVCC可以降低死锁的风险。
比锁定的优势
使用MVCC多版本并发控制比锁定模型的主要优点是在MVCC里, 对检索(读)数据的锁要求与写数据的锁要求不冲突, 所以读不会阻塞写,而写也从不阻塞读。
在数据库里也有表和行级别的锁定机制, 用于给那些无法轻松接受 MVCC 行为的应用。 不过,恰当地使用 MVCC 总会提供比锁更好地性能。
GBase8的特性
在 GBase 中的查询功能通过 MVCC 提供的一致性非锁读(在下文我们简称为一致性读),就是提供通过数据库在一个时间点上的快照来实现信息的查询。查询只是对那些在这个时间点之前提交的事务所做的变更,而并不关注在时间点之后的变更或未提交的事务。当然,若是该事务自身进行的变更,对于查询是可见的。
GBase 的默认级别是 READ COMMITTED ,在该隔离级别下事务中的查询语句,使用当前时间戳进行一致性读,每次查询的时间戳是不相同的。
但对 REPEATABLE READ 隔离级别,在同一个事务中的所有一致性读,使用的时间戳均是第一个查询的时间戳,这样读取的也就是由该事务第一次读建立起来的数据快照。用户只有通过提交当前事务,并发出一个新的查询才会得到新的数据快照。
一致性读是 GBase 在 READ COMMITTED 和 REPEATABLE READ 隔离级别下,处理 SELECT 语句中使用的默认模式。一致性读在它读的数据上不设置任何锁,因此在一致性读某个表的同时,其它用户均可以修改这个表。
注意在 DROP TABLE 和 ALTER TABLE 运作时,一致性读无效 。一致性读在 DROP TABLE 上无效是因为 GBase 不能使用已经 drop 的表,该表已经删除。一致性读在 ALTER TABLE 上无效是因为 GBase 会在事务内,重新创建一个新表并从旧表向新表插入记录。这样当用户再次执行一致性读时,在新表中将看不到任何行,因为在新表中的数据都在第一次一致性读的快照之外。
1.1.什么是当前读和快照读?
在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读
和快照读
?
1.1.1.当前读
像select lock in share mode(共享锁
), select for update ; update, insert ,delete(排他锁
)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
在MySQL中,当前读是一种读取数据的操作方式,它可以直接读取最新的数据版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。MySQL提供了两种实现当前读的机制:
一致性读(Consistent Read):
- 默认隔离级别下(可重复读),MySQL使用一致性读来实现当前读。
- 在事务开始时,MySQL会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。
- 在事务执行期间,无论其他事务对数据进行了何种修改,事务始终使用一致性视图来读取数据。
- 这样可以保证在同一个事务内多次查询返回的结果是一致的,从而实现了当前读。
锁定读(Locking Read):
- 锁定读是一种特殊情况下的当前读方式,在某些场景下使用。
- 当使用锁定读时,MySQL会在执行读取操作前获取共享锁或排他锁,以确保数据的一致性。
- 共享锁(Shared Lock)允许多个事务同时读取同一数据,而排他锁(Exclusive Lock)则阻止其他事务读取或写入该数据。
- 锁定读适用于需要严格控制并发访问的场景,但由于加锁带来的性能开销较大,建议仅在必要时使用。
下面列举的这些语法都是当前读:
语法 |
---|
SELECT ... LOCK IN SHARE MODE |
SELECT ... FOR UPDATE |
UPDATE |
DELETE |
INSERT |
当前读实际上是一种加锁的操作,是悲观锁的实现。
1.1.2.快照读
像不加锁
的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读
, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。
快照读是在读取数据时读取一个一致性视图中的数据,MySQL使用 MVCC 机制来支持快照读。
具体而言,每个事务在开始时会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。这个一致性视图会记录当前事务开始时已经提交的数据版本。
当执行查询操作时,MySQL会根据事务的一致性视图来决定可见的数据版本。只有那些在事务开始之前已经提交的数据版本才是可见的,未提交的数据或在事务开始后修改的数据则对当前事务不可见。
像不加锁的 select 操作就是快照读,即不加锁的非阻塞读。
快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
注意:快照读的前提是隔离级别不是串行级别,在串行级别下,事务之间完全串行执行,快照读会退化为当前读。
MVCC主要就是为了实现读-写冲突不加锁,而这个读指的就是快照读,是乐观锁的实现。
1.2.当前读,快照读和MVCC的关系
- 准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念
- 而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现
- 要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由
3个隐式字段
,undo日志
,Read View
等去完成的,具体可以看下面的MVCC实现原理
1.3.MVCC能解决什么问题,好处是?
数据库并发场景有三种,分别为:
读-读
:不存在任何问题,也不需要并发控制读-写
:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读写-写
:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
备注:第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了;第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失
MVCC带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突
的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以MVCC可以为数据库解决以下问题
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
小结一下咯
总之,MVCC就是因为大牛们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以我们可以形成两个组合:
MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突MVCC + 乐观锁
MVCC解决读写冲突,乐观锁解决写写冲突
这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题
二、MVCC的实现原理
MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突
,它的实现原理主要是依赖记录中的 3个隐式字段
,undo日志
,Read View
来实现的。所以我们先来看看这个三个point的概念
2.1.隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID
,DB_ROLL_PTR
,DB_ROW_ID
等字段
DB_TRX_ID
6byte,最近修改(修改/插入
)事务ID:记录创建这条记录/最后一次修改该记录的事务IDDB_ROLL_PTR
7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)DB_ROW_ID
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID
产生一个聚簇索引- 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
如上图,DB_ROW_ID
是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID
是当前操作该记录的事务ID,而DB_ROLL_PTR
是一个回滚指针,用于配合undo日志,指向上一个旧版本
2.2.undo日志
undo log主要分为两种:
- insert undo log
代表事务在insert
新记录时产生的undo log
, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃 - update undo log
事务在进行update
或delete
时产生的undo log
; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge
线程统一清除
purge
从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。
对MVCC有帮助的实质是update undo log
,undo log
实际上就是存在rollback segment
中旧记录链,它的执行流程如下:
一、 比如一个有个事务插入persion表插入了一条新记录,记录如下,name
为Jerry, age
为24岁,隐式主键
是1,事务ID
和回滚指针
,我们假设为NULL
二、 现在来了一个事务1
对该记录的name
做出了修改,改为Tom
- 在
事务1
修改该行(记录)数据时,数据库会先对该行加排他锁
- 然后把该行数据拷贝到
undo log
中,作为旧记录,既在undo log
中有当前行的拷贝副本 - 拷贝完毕后,修改该行
name
为Tom,并且修改隐藏字段的事务ID为当前事务1
的ID, 我们默认从1
开始,之后递增,回滚指针指向拷贝到undo log
的副本记录,既表示我的上一个版本就是它 - 事务提交后,释放锁
三、 又来了个事务2
修改person表
的同一个记录,将age
修改为30岁
- 在
事务2
修改该行数据时,数据库也先为该行加锁 - 然后把该行数据拷贝到
undo log
中,作为旧记录,发现该行记录已经有undo log
了,那么最新的旧数据作为链表的表头,插在该行记录的undo log
最前面 - 修改该行
age
为30岁,并且修改隐藏字段的事务ID为当前事务2
的ID, 那就是2
,回滚指针指向刚刚拷贝到undo log
的副本记录 - 事务提交,释放锁
从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log
成为一条记录版本线性表,既链表,undo log
的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)
2.3.Read View(读视图)
什么是Read View?
什么是Read View,说白了Read View就是事务进行快照读
操作的时候生产的读视图
(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
所以我们知道 Read View
主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View
读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log
里面的某个版本的数据。
Read View
遵循一个可见性算法,主要是将要被修改的数据
的最新记录中的DB_TRX_ID
(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID
跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR
回滚指针去取出Undo Log
中的DB_TRX_ID
再比较,即遍历链表的DB_TRX_ID
(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID
, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本
那么这个判断条件是什么呢?
我们这里盗窃@呵呵一笑百媚生一张源码图,如上,它是一段MySQL判断可见性的一段源码,即changes_visible
方法(不完全哈,但能看出大致逻辑),该方法展示了我们拿DB_TRX_ID去跟Read View某些属性进行怎么样的比较
在展示之前,我先简化一下Read View,我们可以把Read View简单的理解成有三个全局属性
trx_list
(名字我随便取的)
一个数值列表,用来维护Read View生成时刻系统正活跃的事务IDup_limit_id
记录trx_list列表中事务ID最小的IDlow_limit_id
ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
- 首先比较
DB_TRX_ID < up_limit_id
, 如果小于,则当前事务能看到DB_TRX_ID
所在的记录,如果大于等于进入下一个判断 - 接下来判断
DB_TRX_ID 大于等于 low_limit_id
, 如果大于等于则代表DB_TRX_ID
所在的记录在Read View
生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断 - 判断
DB_TRX_ID
是否在活跃事务之中,trx_list.contains(DB_TRX_ID)
,如果在,则代表我Read View
生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在Read View
生成之前就已经Commit了,你修改的结果,我当前事务是能看见的
2.4.整体流程
我们在了解了隐式字段
,undo log
, 以及Read View
的概念之后,就可以来看看MVCC实现的整体流程是怎么样了
整体的流程是怎么样的呢?我们可以模拟一下
- 当
事务2
对某行数据执行了快照读
,数据库为该行数据生成一个Read View
读视图,假设当前事务ID为2
,此时还有事务1
和事务3
在活跃中,事务4
在事务2
快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1,3的ID,维护在一个列表上,假设我们称为trx_list
事务1 | 事务2 | 事务3 | 事务4 |
---|---|---|---|
事务开始 | 事务开始 | 事务开始 | 事务开始 |
… | … | … | 修改且已提交 |
进行中 | 快照读 | 进行中 | |
… | … | … |
- Read View不仅仅会通过一个列表
trx_list
来维护事务2
执行快照读
那刻系统正活跃的事务ID,还会有两个属性up_limit_id
(记录trx_list列表中事务ID最小的ID),low_limit_id
(记录trx_list列表中事务ID最大的ID,也有人说快照读那刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值+1
,我更倾向于后者 >>>资料传送门 | 呵呵一笑百媚生的回答) ;所以在这里例子中up_limit_id
就是1,low_limit_id
就是4 + 1 = 5,trx_list集合的值是1,3,Read View
如下图
- 我们的例子中,只有
事务4
修改过该行记录,并在事务2
执行快照读
前,就提交了事务,所以当前该行当前数据的undo log
如下图所示;我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID
去跟up_limit_id
,low_limit_id
和活跃事务ID列表(trx_list)
进行比较,判断当前事务2
能看到该记录的版本是哪个。
- 所以先拿该记录
DB_TRX_ID
字段记录的事务ID4
去跟Read View
的的up_limit_id
比较,看4
是否小于up_limit_id
(1),所以不符合条件,继续判断4
是否大于等于low_limit_id
(5),也不符合条件,最后判断4
是否处于trx_list
中的活跃事务, 最后发现事务ID为4
的事务不在当前活跃事务列表中, 符合可见性条件,所以事务4
修改后提交的最新结果对事务2
快照读时是可见的,所以事务2
能读到的最新数据记录是事务4
所提交的版本,而事务4提交的版本也是全局角度上最新的版本
- 也正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同
三、MVCC相关问题
3.1.RR是如何在RC级的基础上解决不可重复读的?
3.1.1.当前读和快照读在RR级别下的区别
表1:
事务A | 事务B |
---|---|
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | 快照读查询金额为500 |
更新金额为400 | |
提交事务 | |
select 快照读 金额为500 | |
select lock in share mode当前读 金额为400 |
在上表的顺序下,事务B的在事务A提交修改后的快照读是旧版本数据,而当前读是实时新数据400
表2:
事务A | 事务B |
---|---|
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | |
更新金额为400 | |
提交事务 | |
select 快照读 金额为400 | |
select lock in share mode当前读 金额为400 |
而在表2
这里的顺序中,事务B在事务A提交后的快照读和当前读都是实时的新数据400,这是为什么呢?
- 这里与上表的唯一区别仅仅是
表1
的事务B在事务A修改金额前快照读
过一次金额数据,而表2
的事务B在事务A修改金额前没有进行过快照读。
所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力
我们这里测试的是更新
,同时删除
和更新
也是一样的,如果事务B的快照读是在事务A操作之后进行的,事务B的快照读也是能读取到最新的数据的
3.2.RC,RR级别下的InnoDB快照读有什么不同?
正是Read View
生成时机的不同,从而造成RC,RR级别下快照读的结果的不同
- 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
- 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
- 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因
总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。
四、最后留个问题
MVCC过程中会加锁吗?加锁和不加锁有什么区别?加锁和不加锁有什么影响?
欢迎小伙伴们积极留言。
手抖人怂枪不稳 ོ 2024/07/09 19:34
最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
我从清晨走过,也拥抱夜晚的星辰,人生没有捷径,你我皆平凡,你好,陌生人,一起共勉。