侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

mvcc的两种层次的理解

2022-07-07 星期四 / 0 评论 / 0 点赞 / 64 阅读 / 12089 字

mvcc是什么百度百科:Multi-Version Concurrency Control 多版本并发控制,MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。一种理解《高性

mvcc是什么

百度百科:Multi-Version Concurrency Control 多版本并发控制,MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。

一种理解

《高性能mysql》Page12
注:这里说的都是Repeatable Read
可以认为mvcc是行级锁的一个变种,但是他在很多情况下避免了加锁操作,因此开销更低。
mvcc的实现是通过保存数据在某个时间点的快照实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的

InnoDb的简化版行为:

InnoDb的mvcc,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面看一下,在repeatable read下,mvcc具体是怎么操作的。

SELECT:

根据以下两个条件检查每行记录:
a:只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或者等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始之前已经存在的,要么是事务自身插入或者修改过的。
b:行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

INSERT:

为新插入的每一行保存当前系统版本号作为行版本号。

DELETE:

为删除的每一行保存当前系统版本号作为行删除标识。

UPDATE:

插入一行新纪录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

另一种理解

..小册《MySQL 是怎样运行的:从根儿上理解 MySQL》

基础知识:

undo日志:

每对记录做改动时,需要把回滚所需的东西记录下来,比如把记录的旧值记下来。这些为了回滚而记录的东西称为undo日志。

trx_id:

行记录中对这个聚簇索引记录做改动的语句所在的事务的事务ID。

roll_pointer:

本质就是一个指针,指向记录对应的undo日志。

undo页面链表:

对每条记录进行改动前,都需要记录undo日志,所以在事务执行过程中可能产生很多的undo日志,因此用链表来存放。

mvcc版本链:

记录的每次更新,都会将旧值放入一条undo日志中,随着更新次数的增多,所有的版本都会被roll_pointer连接成一个链表,称之为版本链,版本链的头节点就是当前记录最新的值。
例子:
我们现在有这张表,开启两个事务,分别做一些操作。

此刻,版本链如图:

由于需要判断版本链中哪个版本是当前事务可见的,引出一个概念:

ReadView:

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的生成规则。

可重复读(重点,注意与第一种理解对照)

在第一次读取时生成ReadView

版本链如图

select分析

第三个事务进行第一个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没提交,所以不会读到其他事务未提交的数据)

此刻将事务1提交

第二次select分析:

复用之前的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之后提交(活跃事务,不可见),这样就达成了目的:可重复读。

读已提交

每次读取数据前都生成ReadView

第一次select分析

这时的过程跟可重复读一模一样。

此刻将事务1提交

第二次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那段流程跟小册一样的,我打算再写一篇不一样流程的具体分析,是一个面试题,尽量明天或者后天完成。

.
.

广告 广告

评论区