Hi,你们好!我是白日梦。html
今天我要跟你分享的话题是:“MySQL是如何根据undo log 链条实现read view机制的?谈谈看”mysql
MySQL单进程多线程的数据库软件,在事务的并发操做中可能会出现脏读,不可重复读,幻读。sql
MySQL支持的四种事务隔离级别以下:数据库
Read uncommited多线程
简单来讲就是:事务A能够读到事务B未commit的数据。这种状况也被叫作脏读。并发
Read commitedmvc
简单来讲就是:事务A能够读到事务B已经commit的数据。线程
Serializable指针
在该级别下,写会加写锁、读会加读锁,除了读读不互斥,其余组合都互斥,所以能够保证事务串行化顺序执行,能够避免脏读、不可重复读与幻读。htm
Repeatable read
以下图:可重复读要求事务A两次 select 查询出来的结果是同样的,即便中间事务B将id=1的行给修改了,也要保证事务A再读取时,读到的结果也得和第一次读到的结果相同。
可是可重复读存在幻读读问题,好比事务A开启后按某个范围X读取一次(事务未提交),这时其余事务在该范围X内插入了新的数据,事务A再读时就会将新插入的数据读取出来,固然在MySQL的RR隔离级别下不会再出现这种幻行的问题。
问题的解决得益于:MVCC多版本并发控制的快照读和next-key lock 当前读。
以RR隔离级别为例:
你能够像下面这样看一下你的MySQL默认使用的什么隔离级别:
MVCC多版本并发控制也被称为快照读,在RR的隔离级别下,当事务开启时会建立一个视图(Read View),其实这个视图就是所谓的快照。在整个事务存在的期间,一直会使用这个视图。
下面看一个九个步骤的小实验:
上图中的右部分的会话中begin以后,就会建立读视图,因此它的屡次select使用的是同一个视图,因此结果都是同样的。即便数据中途被左边的事务更改了,它也没有受到影响。
再结合视图去理解这个过程。
当你执行begin开启事务以后,MySQL会拍下像下图这样的快照:
上图中的trx_ids中记录着MySQL中活跃的且未提交的事务。
假设有事务A、事务B擦很少在同一时刻开启,那这两个事务会分别获得以下的视图。
在RR的隔离级别下,事务一开启就会获得上图那样的ReadView,而且只要事务不提交这个ReadView就一直有效。
就上图来讲:
在事务A的视图中,它的事务ID=61,此时活跃的事务集合是[6一、62],活跃的事务ID中最小的事务id是它自己。下一个事务id应该是63。
在事务B的视图中,它的事务ID=621,此时活跃的事务集合是[6一、62],活跃的事务ID中最小的事务id是61。下一个事务id应该是63。
先让事务A尝试去读取name列的数据。
它会发现的这行数据的Data_TRX_ID=60,经过和trx_ids对比发现这个事务ID不在活跃的事务id集合trx_ids中,而且小于它自己的60。说明:在事务A开启以前,事务ID=60的事务早就提交过了。因此事务A能直接这行数据name = tom。
而后事务B经过update语句尝试去修改这行数据,想将name 改为 jetty。这时MySQL会记录相应的undo log,并以链表的方式串联起来,因而咱们会获得下图:
你能够看到上图中,因为事务B将name改为jerry,致使多出一条undo log。这条undo对应的事务ID=事务B的事务ID = 62。而且经过一个指针执向它的上一个undo log记录。
这时若是事务A从新去读,首先它会读取到的记录是name = jerry,可是它也会发现该记录的trx_id = 62 , 比本身的61还大,而且比下一个事务ID63小。说明:它读到记录实际上是和本身同时开启的事务修改后的产物,这时他就会沿着undo log链条往前找,直到找到第一个trx_id等于或者小于本身事务ID的记录为止。因此事务A再一次读取到trx_id = 60的记录。
这也就是所谓的快照读机制。
另外须要注意的是:就上例来讲,在RR的隔离级别下,确实能保证事务A每次读取出来的结果都是同样的,并且在事务B将其修改后,事务A依然能读取出name = tom。可是这时name=tom真的只是个快照,本质上它已经能够算是不存在是数据了。
本文是第15篇,全文近100篇,点击查看目录
在RR隔离级别下,当事务一开始视图就会被建立出来,而且一直到该事务提交该视图都有效。
在Read Commited隔离级别,每次select 都会建立一个新的视图。
仍是使用这个例子:假设事务A和事务B并发开启,而且各自获得了图中的ReadView。而后很快,事务B就将数据name = tom改为了name = jerry(未提交)。那这时事务A去select会检索出什么结果呢?
事务A检索过程:事务A首先会沿着undo log链条从头开始找,因而它首先找到name = jerry的列。可是它也发现该列的trx_id = 62 不但比本身的事务ID60大,并且还在trx_ids这个活跃事务列表中,说明name = jerry是被和本身差很少同时开启的其余事务更改的。它天然也就读不到。
紧接着事务B提交事务,而后事务A从新select会开启一个新的视图,获得以下图:
当事务A沿着undo log链条往下查找时,他发现首先发现的name = jerry的行的trx_id是62,居然比本身的事务ID61还大,可是进一步发现,这个事务ID62并不在trx_ids中。说明,这个实际上是已经被提交了的数据,那直接就意味着其实本身是容许读出这条数据的。这也就是所谓的读已提交机制。