百度百科:Multi-Version Concurrency Control 多版本并发控制,MVCC是一种并发控制的方法,通常在数据库管理系统中,实现对数据库的并发访问。mysql
《高性能mysql》Page12
注:这里说的都是Repeatable Read
能够认为mvcc是行级锁的一个变种,可是他在不少状况下避免了加锁操做,所以开销更低。
mvcc的实现是经过保存数据在某个时间点的快照实现的。也就是说,无论须要执行多长时间,每一个事务看到的数据都是一致的。根据事务开始的时间不一样,每一个事务对同一张表,同一时刻看到的数据多是不同的。面试
InnoDb的mvcc,是经过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的建立时间,一个保存行的过时时间(或删除时间)。固然存储的不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会做为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面看一下,在repeatable read下,mvcc具体是怎么操做的。spring
根据如下两个条件检查每行记录:
a:只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或者等于事务的系统版本号),这样能够确保事务读取的行,要么是在事务开始以前已经存在的,要么是事务自身插入或者修改过的。
b:行的删除版本要么未定义,要么大于当前事务版本号。这能够确保事务读取到的行,在事务开始以前未被删除。sql
为新插入的每一行保存当前系统版本号做为行版本号。数据库
为删除的每一行保存当前系统版本号做为行删除标识。并发
插入一行新纪录,保存当前系统版本号做为行版本号,同时保存当前系统版本号到原来的行做为行删除标识。mvc
掘金小册《MySQL 是怎样运行的:从根儿上理解 MySQL》框架
每对记录作改动时,须要把回滚所需的东西记录下来,好比把记录的旧值记下来。这些为了回滚而记录的东西称为undo日志。性能
行记录中对这个聚簇索引记录作改动的语句所在的事务的事务ID。学习
本质就是一个指针,指向记录对应的undo日志。
对每条记录进行改动前,都须要记录undo日志,因此在事务执行过程当中可能产生不少的undo日志,所以用链表来存放。
记录的每次更新,都会将旧值放入一条undo日志中,随着更新次数的增多,全部的版本都会被roll_pointer链接成一个链表,称之为版本链,版本链的头节点就是当前记录最新的值。
例子:
咱们如今有这张表,开启两个事务,分别作一些操做。
此刻,版本链如图:
m_ids:生成ReadView时系统中活跃的事务列表。
min_trx_id: m_ids中的最小值。
max_trx_id: 生成ReadView时系统应该分给下一个事务的id值。
creator_trx_id: 生成ReadView的事务的事务id。 (只有增,删,改时才会分配事务ID,只读的化,此值为0)
1:若是被访问记录的trx_id与creator_trx_id相同,即当前事务在访问它本身修改过的记录,可见。
2:记录的trx_id小于min_trx_id值,表示生成该版本的事务已经提交,可见。
3:记录的trx_id大于max_trx_id值,表明生成该版本的事务在当前事务以后才开启,不可见。
4:min_trx_id < trx_id < max_trx_id,判断trx_id是否在m_ids中,若是在,说明建立ReadView时(注意这里是ReadView,而不是trx_id)该版本的事务仍是活跃的,该版本不能够被访问;若是不在,说明建立ReadView时生成该版本的事务已经提交,该版本能够被访问。
根据版本链,依次寻找可见的节点,找到了就返回,若是没有可见的,说明该条记录对该事务不可见。
到这里,是否是感受跟《高性能Mysql》中的mvcc有一点接近了,可是并不同?别急,慢慢来,关键就在于ReadView的生成规则。
第三个事务进行第一个select时,m_ids = [2,3],min_trx_id=2,max_trx_id=4,creator_trx_id=0(由于如今只有读)。
对于 1 c 2 来讲, 2属于[2,3],不可见。
对于 1 b 2 来讲,2属于[2,3],不可见。
对于1 a 1 来讲,1<min_trx_id(即2),可见,所以读取到的就是a。(这也符合咱们对可重复读的认知:解决了脏读。由于事务1没提交,因此不会读到其余事务未提交的数据)
复用以前的ReadView,即m_ids=[2,3],min_trx_id=2,max_trx_id=4,creator_trx_id=0
对于 1 e 3 来讲,3属于[2,3],不可见
对于 1 d 3 来讲,3属于[2,3],不可见
对于 1 c 2 来讲,2属于[2,3],不可见
对于 1 b 3 来讲,2属于[2,3],不可见
对于 1 a 1来讲,1<min_trx_id,可见,所以读到的是a 。(这符合咱们对可重复读的认知:解决了不可重复读。)
总结一下,其实很简单,可重复读时,第一次select生成ReadView,根据ReadView可见的规则,本事务Begin前的事务都可见,读取的时刻以后的事务均不可见,之间的事务若是是本身就可见,若是活跃(即select时其余事务还没提交)不可见,不活跃(select时其余事务已提交)可见。
此事务中的非第一次select,其余事务要么已经提交(第一次读时就可见),要么第一次select以后提交(活跃事务,不可见),这样就达成了目的:可重复读。
这时的过程跟可重复读如出一辙。
第二次select,从新生成ReadView,m_ids=[3],min_trx_id=3,max_trx_id=4,creator_trx_id=0;(其实就是min_trx_3由2变成3,m_ids不包括2了)
对于 1 e 3 来讲,3属于[3],不可见
对于 1 d 3 来讲,3属于[3],不可见
对于 1 c 2 来讲,2<3,可见
所以读到的是c,这也符合咱们对读已提交的理解,第一个事务已经提交了,天然能够看到他提交的c了。
读取记录的最新版本便可
经过加锁的方式访问(之后有空再写)
关于框架,有人跟我说,背背面试题就行,问的就是面试题那些东西,当我研究了一点源码后,我发现,面试题的总结还真的就是至关精髓,我总结的还不如直接背面试题来的归纳、抽象、精准。并且,本身看+总结,就算当时弄清楚了,面试的时候真的记得住那么多细节吗?
就好比隔离级别的几种表现,ReadView说到底也仍是为了实现隔离级别,表如今外界看来,那就是隔离级别。我真的能记得住ReadView的细节吗?我估计是不可能,目测我也就能记住可重复读只在第一次select时生成ReadView,读已提交每次select都生成ReadView吧,这是一种快照读吗?这是把当时读取的结果保存下来,下次再读就直接读快照吗?从ReadView的角度来看,固然不是,可是,他表现的不就是快照读吗?生不生成那个快照是叫不叫快照读的关键吗?难道其余地方所谓的快照读也不是我想的那么简单吗?我有点迷茫,这些有什么意义呢?
可是,回忆起我背诵springmvc执行流程的时候,我想我在面试官面前背书的时候,个人眼神必定透露着迷茫,真正看过springmvc执行流程以后,就算我面试用词不许确,容易遗漏,但我应该是自信的。也许,这就是学习的意义吧。
感受学到了东西的,或者感受我写的稀巴烂可是想追根朔源想去知识源头的,强烈建议购买掘金的mysql小册。 回头看了下小册第一章,“还有各位写博客的同窗,引用的少了叫借鉴,引用的多了就,就有点那个了。但愿各位不要大段大段的复制粘贴,用本身的话写出来的知识才是本身的东西。”,汗,我也不知道我这算不算有点那个了,我尽可能多用用本身的话,多说说本身的理解吧。 sql那段流程跟小册同样的,我打算再写一篇不同流程的具体分析,是一个面试题,尽可能明天或者后天完成。