导言
本文为社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
在《MySQL锁机制》这篇文章中,我们全面剖析了MySQL
提供的锁机制,关于并发业务一般能够通过其提供的各类锁,去保证各场景下的线程安全问题,然后能够避免脏写、脏读、不行重复读及幻读这类问题呈现。
不过成也萧何败也萧何,尽管
MySQL
提供的锁机制确实能处理并发业务带来的一系列问题,但由于加锁后会让一部分业务串行化,而MySQL
本身便是依据磁盘完成的,功能无法跟内存型数据库娉美,因而并发业务串行化会使其效率更低。
也正是由于上述原因,因而MySQL
官方在规划时,抓破脑袋的想:有没有办法再快一点!!终究,MVCC
机制就诞生了,相较于加锁串行化履行,MVCC
机制的呈现,则以另一种形式处理了并发业务造成的问题。
一、并发业务的四种场景
并发业务中又会分为四种状况,分别是读-读、写-写、读-写、写-读,这四种状况分别对应并发业务履行时的四种场景,为了后续剖析MVCC
机制时便利了解,因而先将这几种状况阐明,我们首要来看看读-读场景。
1.1、读-读场景
读-读场景即是指多个业务/线程在一同读取一个相同的数据,比方业务T1
正在读取ID=88
的行记载,业务T2
也在读取这条记载,两个业务之间是并发履行的。
广为人知的一点:
MySQL
履行查询句子,绝对不会对引起数据的任何变化,因而关于这种状况而言,不需求做任何操作,由于不改动数据就不会引起任何并发问题。
1.2、写-写场景
写-写场景也比较简略,也便是指多个业务之间一同对同一数据进行写操作,比方业务T1
对ID=88
的行记载做修正操作,业务T2
则对这条数据做删去操作,业务T1
提交业务后想查询看一下,哦豁,成果连这条数据都不见了,这也是所谓的脏写问题,也被称为更新掩盖问题,关于这个问题在一切数据库、一切阻隔等级中都是零容忍的存在,最低的阻隔等级也要处理这个问题。
1.3、读-写、写-读场景
读-写、写-读实践上从微观视点来看,能够了解成同一种类型的操作,但从微观视点而言则是两种不同的状况,读-写是指一个业务先开端读,然后另一个业务则过来履行写操作,写-读则相反,首要是读、写产生的前后次序的区别。
并发业务中一起存在读、写两类操作时,这是最简略出问题的场景,脏读、不行重复读、幻读都出自于这种场景中,当有一个业务在做写操作时,读的业务中就有或许呈现这一系列问题,因而数据库才会引入各种机制处理。
1.4、各场景下处理问题的计划
在《MySQL锁机制》中,关于写-写、读-写、写-读这三类场景,都是使用加锁的计划保证线程安全,但上面说到过,加锁会导致部分业务串行化,因而效率会下降,而MVCC
机制的诞生则处理了这个问题。
先来想象一个问题:加锁的意图是什么?避免脏写、脏读、不行重复读及幻读这类问题呈现。
关于脏写问题,这是写-写场景下会呈现的,写-写场景必须要加锁才能保障安全,因而先将该场景扫除在外。再想想:关于读-写并存的场景中,脏读、不行重复读及幻读问题都出自该场景中,但实践项目中,呈现这些问题的几率本身就比较小,为了避免一些小概念事情,就将一切操纵同一数据的并发读写业务串行化,这好像有些不讲道理呀,就比方:
为了避免自家保险柜中的
3.25
元被偷,所以每天从早到晚一向守着保险柜,这合理吗?并不合理,终究只要千日做贼,那有千日防贼的道理。
因而MySQL
就依据读-写并存的场景,推出了MVCC
机制,在线程安全问题和加锁串行化之间做了必定取舍,让两者之间达到了很好的平衡,即避免了脏读、不行重复读及幻读问题的呈现,又无需对并发读-写业务加锁处理。
咋做到的呢?接下来一同来好好聊一聊大名鼎鼎的
MVCC
机制。
二、MySQL-MVCC机制总述
MVCC
机制的全称为Multi-Version Concurrency Control
,即多版别并发操控技能,首要是为了提升数据库并发功能而规划的,其间选用更好的方法处理了读-写并发抵触,做到即使有读写抵触时,也能够不加锁处理,然后保证了任何时刻的读操作都是非堵塞的。
但与其说是
MySQL-MVCC
机制,还不如说是InnoDB-MVCC
机制,由于在MySQL
很多的开源存储引擎中,简直只要InnoDB
完成了MVCC
机制,相似于MyISAM、Memory
等引擎中都未曾完成,那其他引擎为何不完成呢?不是不想,而是做不到,这跟MVCC
机制的完成原理有关,这点放在后续详细解说~
不过为了更好的了解啥叫MVCC
多版别并发操控,先来看一个日常生活的比方~
2.1、MVCC技能在日常生活中的表现
不知道各位小伙伴中,是否有人做过论坛这类业务的项目,或者相似审阅的业务需求,以的文章为例,此刻来考虑一个场景:
假设我发布了一篇关于《MySQL业务机制》的文章,发布后挺受欢迎的,因而有不少小伙伴在看,其间有一位小伙伴比较仔细,文中存在两三个错别字,被这位小伙伴指出来了,因而我去批改错别字后从头发布。
问题来了,关于文章初次发布也好,从头发布也罢,绝对要等审阅经往后才会正式发布的,那我批改文章后从头发布,文章又会进入「审阅中」这个状态,此刻关于其他正在看、预备看的小伙伴来说,文章是不是就不见了?终究文章还在审阅撒,因而对这个业务需求又该怎么完成呢?多版别!
啥意思呢?也便是说,关于初次发布后通过审阅的文章,在后续从头发布审阅时,用户能够看到更新前的文章,也便是看到老版别的文章,当更新后的文章审阅经往后,再运用新版别的文章代替老版别的文章即可。
这样就能做到新老版别的兼容,也能够保证文章批改时,其他正在阅览的小伙伴不会受影响,而MySQL-MVCC
机制的思想也大致相同。
2.2、MySQL-MVCC多版别并发操控
MySQL
中的多版别并发操控,也和上面给出的比方相似,终究回想一下,脏读、不行重复读、幻读问题都是由于多个业务并发读写导致的,但这些问题都是依据最新版别的数据并发操作才会呈现,那假如读、写的业务操作的不是同一个版别呢?比方写操作走新版别,读操作走老版别,这样是不是无论履行写操作的业务干了啥,都不会影响读的业务?答案是Yes
。
不过要略微记住,
MySQL
中仅在RC
读已提交等级、RR
可重复读等级才会运用MVCC
机制,Why
?
由于假如是RU
读未提交等级,既然都允许存在脏读问题、允许一个业务读取另一个业务未提交的数据,那天然能够直接读最新版别的数据,因而无需MVCC
介入。
一起如若是Serializable
串行化等级,由于会将一切的并发业务串行化处理,也便是不管业务是读操作,亦或是写操作,都会被排好队一个个履行,这都不存在所谓的多线程并发问题了,天然也无需MVCC
介入。
因而要牢记:
MVCC
机制在MySQL
中,仅有InnoDB
引擎支持,而在该引擎中,MVCC
机制只对RC、RR
两个阻隔等级下的业务收效。当然,RC、RR
两个不同的阻隔等级中,MVCC
的完成也存在少许差异,关于这点后续详细解说。
三、MySQL-MVCC机制完成原理剖析
OK~,简略了解了啥叫MVCC
机制后,接着一同来看看InnoDB
引擎是怎么完成它的,MVCC
机制首要通过躲藏字段、Undo-log
日志、ReadView
这三个东西完成的,因而这三玩意儿也被称为“MVCC
三剑客”!废话不多说,一同来看看。
3.1、InnoDB表的躲藏字段
一般而言,当你依据InnoDB
引擎树立一张表后,MySQL
除开会构建你显式声明的字段外,一般还会构建一些InnoDB
引擎的躲藏字段,在InnoDB
引擎中首要有DB_ROW_ID、DB_Deleted_Bit、DB_TRX_ID、DB_ROLL_PTR
这四个躲藏字段,挨个简略介绍一下。
3.1.1、躲藏主键 – ROW_ID(6Bytes)
在之前介绍《索引原理篇》的时候聊到过一点,关于InnoDB
引擎的表而言,由于其表数据是依照聚簇索引的格式存储,因而一般都会挑选主键作为聚簇索引列,然后依据主键字段构建索引树,但如若表中未界说主键,则会挑选一个具备仅有非空属性的字段,作为聚簇索引的字段来构建树。
当两者都不存在时,
InnoDB
就会隐式界说一个次序递加的列ROW_ID
来作为聚簇索引列。
因而要牢记一点,假如你挑选的引擎是InnoDB
,就算你的表中未界说主键、索引,其实默认也会存在一个聚簇索引,只不过这个索引在上层无法运用,仅提供给InnoDB
构建树结构存储表数据。
3.1.2、删去标识 – Deleted_Bit(1Bytes)
在之前讲《SQL履行篇-写SQL履行原理》时,我们只粗略的过了一下大体流程,其间并未涉及到一些细节论述,在这里略微提一下:关于一条delete
句子而言,当履行后并不会立马删去表的数据,而是将这条数据的Deleted_Bit
删去标识改为1/true
,后续的查询SQL
检索数据时,假如检索到了这条数据,但看到躲藏字段Deleted_Bit=1
时,就知道该数据现已被其他业务delete
了,因而不会将这条数据归入成果集。
OK~,但规划
Deleted_Bit
这个躲藏字段的好处是什么呢?首要是能够有利于聚簇索引,比方当一个业务中删去一条数据后,后续又履行了回滚操作,假设此刻是真正的删去了表数据,会产生什么状况呢?
- ①删去表数据时,有或许会损坏索引树原本的结构,导致呈现叶子节点兼并的状况。
- ②业务回滚时,又需从头刺进这条数据,再次刺进时又会损坏前面的结构,导致叶子节点割裂。
综上所述,假如履行
delete
句子就删去实在的表数据,由于业务回滚的问题,就很有或许导致聚簇索引树产生两次结构调整,这其间的开支可想而知,并且先删去,再回滚,终究树又变成了原状,那这两次树的结构调整仍是无意义的。
所以,当履行delete
句子时,只会改动将躲藏字段中的删去标识改为1/true
,假如后续业务呈现回滚动作,直接将其标识再改回0/false
即可,这样就避免了索引树的结构调整。
但如若业务删去数据之后提交了业务呢?总不能让这条数据一向留在磁盘吧?终究假如一切的
delete
操作都这么干,就会导致磁盘爆满~,显然这样是不当的,因而删去标识为1/true
的数据终究依旧会从磁盘中移除,啥时候移呢?
在之前讲《Nginx-缓存清理》时,曾经提到过purger
这一系列的参数,通过配置该系列参数后,Nginx
后台中会创立对应的purger
线程去主动删去缓存数据。而MySQL
中也不破例,相同存在purger
线程的概念,为了避免“已删去”的数据占用过多的磁盘空间,purger
线程会主动清理Deleted_Bit=1/true
的行数据。
当然,为了保证清理数据时不会影响
MVCC
的正常作业,purger
线程本身也会保护一个ReadView
,假如某条数据的Deleted_Bit=true
,并且TRX_ID
对purge
线程的ReadView
可见,那么这条数据必定是能够被安全清除的(即不会影响MVCC
作业)。
关于上述终究一段大家或许会有少许疑问,这是由于还未曾介绍ReadView
,因而有些不了解可先跳过,后续了解了ReadView
后再回来看会好很多。
3.1.3、最近更新的业务ID – TRX_ID(6Bytes)
TRX_ID
全称为transaction_id
,翻译过来也便是业务ID
的意思,MySQL
关于每一个创立的业务,都会为其分配一个业务ID
,业务ID
相同遵从次序递加的特性,即后来的业务ID
绝对会比之前的ID
要大,比方:
此刻业务
T1
预备修正表字段的值,MySQL
会为其分配一个业务ID=1
,当业务T2
预备向表中刺进一条数据时,又会为这个业务分配一个ID=2
……
但有一个细节点需求记住:MySQL
关于一切包括写入SQL
的业务,会为其分配一个次序递加的业务ID
,但假如是一条select
查询句子,则分配的业务ID=0
。
不过关于手动敞开的业务,
MySQL
都会为其分配业务ID
,就算这个手动敞开的业务中仅有select
操作。
表中的躲藏字段TRX_ID
,记载的便是最近一次改动当时这条数据的业务ID
,这个字段是完成MVCC
机制的核心之一。
3.1.4、回滚指针 – ROLL_PTR(7Bytes)
ROLL_PTR
全称为rollback_pointer
,也便是回滚指针的意思,这个也是表中每条数据都会存在的一个躲藏字段,当一个业务对一条数据做了改动后,都会将旧版别的数据放到Undo-log
日志中,而rollback_pointer
便是一个地址指针,指向Undo-log
日志中旧版别的数据,当需求回滚业务时,就能够通过这个躲藏列,来找到改动之前的旧版别数据,而MVCC
机制也使用这点,完成了行数据的多版别。
3.2、InnoDB引擎的Undo-log日志
在之前《业务篇》中剖析业务完成原理时,我们得知了MySQL
业务机制是依据Undo-log
完成的,一起在刚刚在聊回滚指针时,聊到了Undo-log
日志中会存储旧版别的数据,但要留意:Undo-log
中并不仅仅只存储一条旧版别数据,其实在该日志中会有一个版别链,啥意思呢?举个比方:
SELECT * FROM `zz_users` WHERE user_id = 1;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 熊猫 | 女 | 6666 | 2022-08-14 15:22:01 |
+---------+-----------+----------+----------+---------------------+
UPDATE `zz_users` SET user_name = "竹子" WHERE user_id = 1;
UPDATE `zz_users` SET user_sex = "男" WHERE user_id = 1;
比方上述这段SQL
隶归于trx_id=1
的T1
业务,其间对同一条数据改动了两次,那Undo-log
日志中只会存储一条旧版别数据吗?NO
,答案是两条旧版别的数据,如下图:
从上图中可显着看出:不同的旧版别数据,会以roll_ptr
回滚指针作为链接点,然后将一切的旧版别数据组成一个单向链表。但要留意一点:最新的旧版别数据,都会刺进到链表头中,而不是追加到链表尾部。
细说一下履行上述
update
句子的详细进程:
①对ID=1
这条要修正的行数据加上排他锁。
②将原本的旧数据拷贝到Undo-log
的rollback Segment
区域。
③对表数据上的记载进行修正,修正完成后将躲藏字段中的trx_id
改为当时业务ID
。
④将躲藏字段中的roll_ptr
指向Undo-log
中对应的旧数据,并在提交业务后释放锁。
为什么Undo-log
日志要规划出版别链呢?两个好处:一方面能够完成业务点回滚(这点回去参阅业务篇),另一方面则能够完成MVCC
机制(这点后面聊)。
与之前的删去标识相似,一条数据被
delete
后并提交了,终究会从磁盘移除,而Undo-log
中记载的旧版别数据,相同会占用空间,因而在业务提交后也会移除,移除的作业相同由purger
线程担任,purger
线程内部也会保护一个ReadView
,它会以此作为判别依据,来决议何时移除Undo
记载。
3.3、MVCC核心 – ReadView
MVCC
在前面聊到过,它翻译过来便是多版别并发操控的意思,关于这个名词中的多版别现现已过Undo-log
日志完成了,但再考虑一个问题:假如T2
业务要查询一条行数据,此刻这条行数据正在被T1
业务写,那也就代表着这条数据或许存在多个旧版别数据,T2
业务在查询时,应该读这条数据的哪个版别呢?此刻就需求用到ReadView
,用它来做多版别的并发操控,依据查询的机遇来挑选一个当时业务可见的旧版别数据读取。
那终究什么是
ReadView
呢?便是一个业务在测验读取一条数据时,MVCC
依据当时MySQL
的运转状态生成的快照,也被称之为读视图,即ReadView
,在这个快照中记载着当时一切活泼业务的ID
(活泼业务是指还在履行的业务,即未结束(提交/回滚)的业务)。
当一个业务发动后,初次履行select
操作时,MVCC
就会生成一个数据库当时的ReadView
,一般而言,一个业务与一个ReadView
归于一对一的联系(不同阻隔等级下也会存在细微差异),ReadView
一般包括四个核心内容:
-
creator_trx_id
:代表创立当时这个ReadView
的业务ID
。 -
trx_ids
:表明在生成当时ReadView
时,体系内活泼的业务ID
列表。 -
up_limit_id
:活泼的业务列表中,最小的业务ID
。 -
low_limit_id
:表明在生成当时ReadView
时,体系中要给下一个业务分配的ID
值。
上面四个值很简略,值得一提的是low_limit_id
,它并不是现在体系中活泼业务的最大ID
,由于之前讲到过,MySQL
的业务ID
是按序递加的,因而当发动一个新的业务时,都会为其分配业务ID
,而这个low_limit_id
则是整个MySQL
中,要为下一个业务分配的ID
值。
下面上个ReadView
的示意图,来好好了解一下它:
假设现在数据库中共有T1~T5
这五个业务,T1、T2、T4
还在履行,T3
现已回滚,T5
现已提交,此刻当有一条查询句子履行时,就会使用MVCC
机制生成一个ReadView
,由于前面讲过,单纯由一条select
句子组成的业务并不会分配业务ID
,因而默认为0
,所以现在这个快照的信息如下:
{
"creator_trx_id" : "0",
"trx_ids" : "[1,2,4]",
"up_limit_id" : "1",
"low_limit_id" : "6"
}
OK~,简略了解ReadView
的结构后,接着一同来聊一聊MVCC
机制的完成原理。
3.4、MVCC机制完成原理
将“MVCC
三剑客”的概念论述完毕后,再结合三者来谈谈MVCC
的完成,其实也比较简略,通过前面的解说后已得知:
- ①当一个业务测验改动某条数据时,会将原本表中的旧数据放入
Undo-log
日志中。 - ②当一个业务测验查询某条数据时,
MVCC
会生成一个ReadView
快照。
其间Undo-log
首要完成数据的多版别,ReadView
则首要完成多版别的并发操控,仍是以之前的比方来举例阐明:
-- 业务T1:trx_id=1
UPDATE `zz_users` SET user_name = "竹子" WHERE user_id = 1;
UPDATE `zz_users` SET user_sex = "男" WHERE user_id = 1;
-- 业务T2:trx_id=2
SELECT * FROM `zz_users` WHERE user_id = 1;
现在存在T1、T2
两个并发业务,T1
现在在修正ID=1
的这条数据,而T2
则预备查询这条数据,那么T2
在履行时详细进程是怎么回事呢?如下:
- ①当业务中呈现
select
句子时,会先依据MySQL
的当时状况生成一个ReadView
。 - ②判别行数据中的躲藏列
trx_id
与ReadView.creator_trx_id
是否相同:- 相同:代表创立
ReadView
和修正行数据的业务是同一个,天然能够读取最新版数据。 - 不相同:代表现在要查询的数据,是被其他业务修正过的,持续往下履行。
- 相同:代表创立
- ③判别躲藏列
trx_id
是否小于ReadView.up_limit_id
最小活泼业务ID
:- 小于:代表改动行数据的业务在创立快照前就已结束,能够读取最新版别的数据。
- 不小于:则代表改动行数据的业务还在履行,因而需求持续往下判别。
- ④判别躲藏列
trx_id
是否小于ReadView.low_limit_id
这个值:- 大于或等于:代表改动行数据的业务是生成快照后才敞开的,因而不能拜访最新版数据。
- 小于:表明改动行数据的业务
ID
在up_limit_id、low_limit_id
之间,需求进一步判别。
- ⑤假如躲藏列
trx_id
小于low_limit_id
,持续判别trx_id
是否在trx_ids
中:- 在:表明改动行数据的业务现在依旧在履行,不能拜访最新版数据。
- 不在:表明改动行数据的业务现已结束,能够拜访最新版的数据。
说简略一点,便是首要会去获取表中行数据的躲藏列,然后通过上述一系列判别后,能够得知:现在查询数据的业务到底能不能拜访最新版的数据。假如能,就直接拿到表中的数据并回来,反之,不能则去Undo-log
日志中获取旧版别的数据回来。
留意:假设
Undo-log
日志中存在版别链怎么办?该获取哪个版别的旧数据呢?
假如Undo-log
日志中的旧数据存在一个版别链时,此刻会首要依据躲藏列roll_ptr
找到链表头,然后依次遍历整个列表,然后检索到最合适的一条数据并回来。但在这个遍历进程中,是怎么判别一个旧版别的数据是否合适的呢?条件如下:
- 旧版别的数据,其躲藏列
trx_id
不能在ReadView.trx_ids
活泼业务列表中。
由于假如旧版别的数据,其trx_id
依旧在ReadView.trx_ids
中,就代表着产生这条旧数据的业务还未提交,天然不能读取这个版别的数据,以前面给出的比方来阐明:
这是由业务T1
生成的版别链,此刻T2
生成的ReadView
如下:
{
"creator_trx_id" : "0",
"trx_ids" : "[1]",
"up_limit_id" : "1",
"low_limit_id" : "2"
}
结合这个ReadView
信息,通过前面那一系列判别后,终究会得到:不能读取最新版数据,因而需求去Undo-log
的版别链中读数据,首要依据roll_ptr
找到第一条旧数据:
此刻发现其trx_id=1
,坐落ReadView.trx_ids
中,因而不能读取这条旧数据,接着再依据这条旧数据的roll_ptr
找到第二条旧版别数据:
这时再看其trx_id=null
,并不坐落ReadView.trx_ids
中,null
表明这条数据在前次MySQL
运转时就已刺进了,因而这条旧版别的数据能够被T2
业务读取,终究T2
就会查询到这条数据并回来。
OK~,终究再来看一个场景!即规模查询时,忽然呈现新增数据怎么办呢?如下:
SELECT * FROM `zz_users`;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 熊猫 | 女 | 6666 | 2022-08-14 15:22:01 |
| 2 | 竹子 | 男 | 1234 | 2022-09-14 16:17:44 |
| 3 | 子竹 | 男 | 4321 | 2022-09-16 07:42:21 |
| 4 | 猫熊 | 女 | 8888 | 2022-09-27 17:22:59 |
| 9 | 黑竹 | 男 | 9999 | 2022-09-28 22:31:44 |
+---------+-----------+----------+----------+---------------------+
-- T1业务:查询ID >= 3 的一切用户信息
select * from `zz_users` where user_id >= 3;
-- T2业务:新增一条 ID = 6 的用户记载
INSERT INTO `zz_users` VALUES(6,"棕熊","男","7777","2022-10-02 16:21:33");
此刻当T1
业务查询数据时,忽然蹦出来一条ID=6
的数据,通过判别之后会发现新增这条数据的业务还在履行,所以要去查询旧版别数据,但此刻由于是新增操作,因而roll_ptr=null
,即表明没有旧版别数据,此刻会不会读取最新版的数据呢?答案是NO
,假如查询数据的业务不能读取最新版数据,一起又无法从版别链中找到旧数据,那就意味着这条数据对T1
业务完全不行见,因而T1
的查询成果中不会包括ID=6
的这条新增记载。
3.5、RC、RR不同等级下的MVCC机制
3.4
阶段现已将MVCC
机制的详细完成进程剖析了一遍,接下来再考虑一个问题:
ReadView
是一个业务中只生成一次,仍是每次select
时都会生成呢?
这个问题的答案跟业务的阻隔机制有关,不同等级的阻隔机制也并不同,假如此刻MySQL
的业务阻隔机制处于RC
读已提交等级,那此刻来看一个比方:
-- 敞开一个业务T1:首要是修正两次ID=1的行数据
begin;
UPDATE `zz_users` SET user_name = "竹子" WHERE user_id = 1;
UPDATE `zz_users` SET user_sex = "男" WHERE user_id = 1;
-- 再敞开一个业务T2:首要是查询ID=1的行数据
SELECT * FROM `zz_users` WHERE user_id = 1;
-- 此刻先提交业务T1
commit;
-- 再次在业务T2中查一次ID=1的行数据
SELECT * FROM `zz_users` WHERE user_id = 1;
先阐明一点,为了便利了解,因而我将两个业务的代码贴在了一块,但如若你要做实践的试验,请切记将
T1、T2
用两个连接来写。
OK~,再来看看上述这个案例,假如是处于RC
等级的状况下,T2
业务中的查询成果如下:
SELECT * FROM `zz_users` WHERE user_id = 1;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 熊猫 | 女 | 6666 | 2022-08-14 15:22:01 |
+---------+-----------+----------+----------+---------------------+
SELECT * FROM `zz_users` WHERE user_id = 1;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 竹子 | 男 | 6666 | 2022-08-14 15:22:01 |
+---------+-----------+----------+----------+---------------------+
为什么两次查询成果不一样呢?由于RC
等级下,MVCC
机制是会在每次select
句子履行前,都会生成一个ReadView
,由于T2
业务中第2次查询数据时,T1
现已提交了,所以第2次查询就能读到修正后的数据,这是啥问题?不行重复读问题。
接着再来看看
RR
可重复等级下的MVCC
机制,SQL
代码和上述一模一样,但查询成果如下:
SELECT * FROM `zz_users` WHERE user_id = 1;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 熊猫 | 女 | 6666 | 2022-08-14 15:22:01 |
+---------+-----------+----------+----------+---------------------+
SELECT * FROM `zz_users` WHERE user_id = 1;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 熊猫 | 女 | 6666 | 2022-08-14 15:22:01 |
+---------+-----------+----------+----------+---------------------+
这又是为啥?为啥分明在T2
业务第2次查询前,T1
现已提交了,T2
依旧查询出的成果和第一次相同呢?这是由于在RR
等级中,一个业务只会在初次履行select
句子时生成快照,后续一切的select
操作都会依据这个ReadView
来判别,这样也就处理了RC
等级中存在的不行重复问题。
终究简略提一嘴:实践上
InnoDB
引擎中,是能够在RC
等级处理脏读、不行重复读、幻读这一系列问题的,可是为了将业务阻隔等级规划的契合DBMS
规范,因而在完成时刻意保留了这些问题,然后放在更高的阻隔等级中处理~
四、MVCC机制篇总结
MVCC
多版别并发操控,听起来好像蛮巨大上的,但实践研究起来会发现它并不杂乱,其间的多版别首要依赖Undo-log
日志来完成,而并发操控则通过表的躲藏字段+ReadView
快照来完成,通过Undo-log
日志、躲藏字段、ReadView
快照这三玩意儿,就完成了MVCC
机制,进程还蛮简略的~
到这里,其实关于
MySQL
的业务阻隔机制,现已拨开一部分迷雾了,下篇《MySQL业务与锁机制原理篇》中,则会彻底讲清楚MySQL
锁是怎么完成的,以及不同的业务阻隔等级,又是怎么凭借锁+MVCC
处理客户端SQL
的,那么我们下篇见~