首页 > 其他分享 >MVCC原理

MVCC原理

时间:2022-11-29 17:56:33浏览次数:51  
标签:事务 记录 ReadView trx 版本 MVCC 原理 id

一、什么是MVCC

MVCC,Multi-Version Concurrency Control,多版本并发控制。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。
 
MVCC只在已提交读(Read Committed)和可重复读(Repeatable Read)两个隔离级别下工作,MVCC的实现原理主要是依赖 每一行记录中两个隐藏字段,undo log,ReadView

一些概念

版本链

对于使⽤InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:

  • trx_id:
    每次⼀个事务对某条聚簇索引记录进⾏改动时,都会把该事务的事务id赋值给trx_id隐藏列。

  • roll_pointer:
    每次对某条聚簇索引记录进⾏改动时,都会把旧的版本写⼊到undo⽇志中,然后这个隐藏列就相当于⼀个指针,可以通过它来找到该记录修改前的信息。

⽐⽅说我们的表hero现在只包含⼀条记录:

mysql>SELECT * FROM hero;
+--------+--------+---------+
|number  |  name  | country |
+--------+--------+---------+
|   1    | 刘备   |    蜀    |
+--------+--------+---------+

假设插⼊该记录的事务id为80,那么此刻该条记录的示意图如下所示:
image

假设之后两个事务id分别为100、200的事务对这条记录进⾏UPDATE操作,操作流程如下:
image

每次对记录进⾏改动,都会记录⼀条undo⽇志,每条undo⽇志也都有⼀个roll_pointer属性(INSERT操作对应的undo⽇志没有该属性,因为该记录并没有更早的版本),可以将这些undo⽇志都连起来,串成⼀个链表,所以现在的情况就像下图⼀样:
image

这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含⽣成该版本时对应的事务id

ReadView

对于使⽤READ UNCOMMITTED隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了;

对于使⽤SERIALIZABLE隔离级别的事务来说,规定使⽤加锁的⽅式来访问记录;

对于使⽤READ COMMITTED和REPEATABLE READ隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另⼀个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核⼼问题就是:需要判断⼀下版本链中的哪个版本是当前事务可⻅的。为此提出了⼀个ReadView的概念。

ReadView中主要包含4个⽐较重要的内容:

  • m_ids:表示在⽣成ReadView时当前系统中活跃的读写事务的事务id列表(未提交的事务)。
  • min_trx_id:表示在⽣成ReadView时当前系统中活跃的读写事务中最⼩的事务id,也就是m_ids中的最⼩值。
  • max_trx_id:表示⽣成ReadView时系统中应该分配给下⼀个事务的id值。

注意max_trx_id并不是m_ids中的最⼤值,事务id是递增分配的。⽐⽅说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么⼀个新的读事务在⽣成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。

  • creator_trx_id:表示⽣成该ReadView的事务的事务id。

只有在对表中的记录做改动时(执⾏INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在⼀个只读事务中的事务id值都默认为0。

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可⻅:

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它⾃⼰修改过的记录,所以该版本可以被当前事务访问。

  • 如果被访问版本的trx_id属性值⼩于ReadView中的min_trx_id值,表明⽣成该版本的事务在当前事务⽣成ReadView前已经提交,所以该版本可以被当前事务访问。

  • 如果被访问版本的trx_id属性值⼤于ReadView中的max_trx_id值,表明⽣成该版本的事务在当前事务⽣成ReadView后才开启,所以该版本不可以被当前事务访问。

  • 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断⼀下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时⽣成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时⽣成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可⻅的话,那就顺着版本链找到下⼀个版本的数据。

READ COMMITTED和REPEATABLE READ隔离级别的的⼀个⾮常⼤的区别就是它们⽣成ReadView的时机不同

  • READ COMMITTED —— 每次读取数据前都⽣成⼀个ReadView
  • REPEATABLE READ —— 在第⼀次读取数据时⽣成⼀个ReadView

示例

READ COMMITTED级别
⽐⽅说现在系统⾥有两个事务id分别为100、200的事务在执⾏:

#	Transaction	100
BEGIN;
UPDATE hero SET name = '关⽻' WHERE number = 1;
UPDATE hero SET name = '张⻜' WHERE number = 1;

#	Transaction	200
BEGIN;
#	更新了⼀些别的表的记录
...
再次强调⼀遍,事务执⾏过程中,只有在第⼀次真正修改记录时(⽐如使⽤INSERT、DELETE、UPDATE语句),才会被分配⼀
个单独的事务id,这个事务id是递增的。所以我们才在Transaction 200中更新⼀些别的表的记录,⽬的是让它分配事务id。

此刻,表hero中number为1的记录得到的版本链表如下所示:
image

假设现在有⼀个使⽤READ COMMITTED隔离级别的事务开始执⾏:

#	使⽤READ COMMITTED隔离级别的事务
BEGIN;
#	SELECT1:Transaction	100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

这个SELECT1的执⾏过程如下:

  • 在执⾏SELECT语句时会先⽣成⼀个ReadView,ReadView的m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。

  • 然后从版本链中挑选可⻅的记录,从图中可以看出,最新版本的列name的内容是'张⻜',该版本的trx_id值为100,在m_ids列表内,所以不符合可⻅性要求,根据roll_pointer跳到下⼀个版本。

  • 下⼀个版本的列name的内容是'关⽻',该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下⼀个版本。

  • 下⼀个版本的列name的内容是'刘备',该版本的trx_id值为80,⼩于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给⽤户的版本就是这条列name为'刘备'的记录。

之后,我们把事务id为100的事务提交⼀下,然后再到事务id为200的事务中更新⼀下表hero中number为1的记录:

#	Transaction	200
BEGIN;
#	更新了⼀些别的表的记录
...

UPDATE hero SET name = '赵云' WHERE number = 1;
UPDATE hero SET name = '诸葛亮' WHERE number = 1;

此刻,表hero中number为1的记录的版本链就⻓这样:
image

然后再到刚才使⽤READ COMMITTED隔离级别的事务中继续查找这个number为1的记录,如下:

#	使⽤READ	COMMITTED隔离级别的事务
BEGIN;
#	SELECT1:Transaction	100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'
#	SELECT2:Transaction	100提交,Transaction	200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'张⻜'

这个SELECT2的执⾏过程如下:

  • 在执⾏SELECT语句时会⼜会单独⽣成⼀个ReadView,该ReadView的m_ids列表的内容就是[200](事务id为100的那个事务已经提交了,所以再次⽣成快照时就没有它了),min_trx_id为200,max_trx_id为201,creator_trx_id为0。

  • 然后从版本链中挑选可⻅的记录,从图中可以看出,最新版本的列name的内容是'诸葛亮',该版本的trx_id值为200,在m_ids列表内,所以不符合可⻅性要求,根据roll_pointer跳到下⼀个版本。

  • 下⼀个版本的列name的内容是'赵云',该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下⼀个版本。

  • 下⼀个版本的列name的内容是'张⻜',该版本的trx_id值为100,⼩于ReadView中的min_trx_id值200,所以这个版本是符合要求的,最后返回给⽤户的版本就是这条列name为'张⻜'的记录。

标签:事务,记录,ReadView,trx,版本,MVCC,原理,id
From: https://www.cnblogs.com/d111991/p/16935941.html

相关文章

  • [资料] 设计原理图资料保存:FMC210-1路1Gsps AD、1路2.5Gsps DA的FMC子卡解决方案
    FMC210-1路1GspsAD、1路2.5GspsDA的FMC子卡  一、板卡概述   FMC-1AD2DA是北京太速科技自主研发的一款1路1GAD采集、1路2.5GDA回放的FMC子......
  • 拓端tecdat|主成分分析(PCA)原理及R语言代写实现及分析实例
    主成分分析(PCA)原理及R语言实现及分析实例 主成分分析(PCA)是一种数据降维技巧,它能将大量相关变量转化为一组很少的不相关变量,这些无关变量称为主成......
  • CI分页器pagination的原理及实现
    以下是本人原创,如若转载和使用请注明转载地址。本博客信息切勿用于商业,可以个人使用,若喜欢我的博客,请关注我,谢谢!​​下面这段代码是从官网上翻译过来的,介绍了分页的用例pub......
  • 浅谈Google蜘蛛抓取的工作原理
    首先,Google蜘蛛寻找新的页面。然后,Google对这些页面进行索引,以了解它们的内容,并根据检索到的数据对它们进行排名。爬行和索引是两个不同的过程,但是,它们都由爬行器执行。......
  • 【数据库系统原理与设计】(六)SQL数据定义、更新及数据库编程
    六.SQL数据定义、更新及数据库编程6.1SQL数据定义语言1. SQL数据定义语言DDL包括: 数据库的定义:创建、修改和删除基本表的定义:创建、修改和删除视图的定义:创建和......
  • hashmap底层原理
    1HashMap的内部数据结构数组+链表/红黑树 2HashMap允许空键空值么HashMap最多只允许一个键为Null(多条会覆盖),但允许多个值为Null 3影响HashMap性能的重要参......
  • 宠物饮水机缺水检测原理
    宠物饮水机越来越智能化,从开始的预留窗口人工查看水位,到目前的传感器自动侦测水位变化并发出提醒。每一个新增的功能都能给用户带来不一样的体验。电容式水位传感器:很多宠物......
  • vue3响应式原理以及ref和reactive区别还有vue2/3生命周期的对比,第二天
    前言:前天我们学了ref和reactive,提到了响应式数据和Proxy,那我们今天就来了解一下,vue3的响应式在了解之前,先复习一下之前vue2的响应式原理vue2的响应式:原理:对......
  • KVC原理与数据筛选
    作者:宋宏帅1前言在技术论坛中看到一则很有意思的KVC案例:@interfacePerson:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,assign)......
  • mybatis SelectKey标签执行原理
    SelectKey标签在mybatis中可以配置成在主sql执行之前和执行之后两种时机进行执行。mybatis执行sql时一次会涉及到这些对象sqlSession-->Executor-->StatementHandler其......