多版本并发控制(MVCC)

news2024/9/29 1:22:54

MVCC机制概述

MVCC(Multi-Version Concurrency Control),中文是多版本并发控制,是指在使用READ COMMITTED、REPEATABLE READ这两种隔离级别的事务在执行SELECT操作时访问记录的版本链的过程,从而在不加锁的前提下使不同事务的读写操作能够并发安全执行,提升系统性能。MVCC机制的核心是在做SELECT操作前会生产一个ReadView,通过这个ReadView可以确认版本链中哪个版本的数据对当前事务可见,通过READ COMMITTD隔离级别的事务在每次进行SELECT操作前都会成1个ReadView,REPEATABLE READ隔离级别的事务只在第1次进行SELECT操作前生成1个ReadView,之后的查询操作都重复使用这个ReadView。通过ReadView找到符合条件的记录版本(记录版本是由undo日志构建的),其实就像是在生成ReadView的那个时刻做了1次快照,因此利用MVCC机制读取数据又叫快照读,也叫一致性读。

  • READ COMMITTD隔离级别和REPEATABLE READ隔离级别可以通过MVCC机制保证,SERIALIZABLE隔离级别是通过加锁保证的,READ UNCOMMITTED隔离级别由于什么措施也没做,因此会允许脏读、不可重复和幻读发生。

快照读和当前读

快照读又叫一致性读,读取的是快照数据。不加锁的简单的select都属于快照读,即不加锁的非阻塞读;比如这样:

select * from player where ...

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。
既然是基于多版本,那么快照读可能读到的并不一定是数据的最终版本,而有可能是之前的历史版本。
当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

MVCC版本链的形成

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

  • trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。注意:只有在对表中的记录做INSERT、DELETE和UPDATE这些修改表中记录的操作时才会给事务分配事务id,且事务id的分配是递增的,一个只读事务的trx_id为0;
  • roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,roll_pointer就相当于一个指针,可以通过它来找到该记录修改前的信息。
    如果此时表中只有1条记录,且插入该记录的事务id为80,此时该记录的行格式简化版如下:
    在这里插入图片描述
    假设之后两个事务id分别为100、200的事务对这条记录进行UPDATE操作,操作流程如下:
    在这里插入图片描述
    每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志串连起来形成一个链表,如下图:
    在这里插入图片描述
    对该记录的每次更新操作(UPDATE)都会将旧值放到一条undo日志中,即对该记录的一个历史版本,随着更新次数的增多产生的undo日志也增多,所有undo日志被roll_pointer属性连接成一个链表,这个链表就是版本链。关于版本链有以下点需要注意:
  • 版本链是针对某条记录的,即是一条用户记录的不同版本组成的链表;
  • 事务COMMIT之前对记录的修改也会放到undo日志,作为记录的一个历史版本组成版本链;
  • 在版本链中插入undo日志是遵循“头插法”,即每次都是将最近生成的undo日志插入在版本链的链表头部,即版本链头结点对应的记录版本是最新的;
  • 查询版本链时,也是从链表头部遍历,即从最新版本的undo日志记录向老版本的undo日志记录遍历查询。

ReadView(快照)

ReadView的形成

对于使用READ UNCOMMITTED隔离级别的事务来说,由于允许读到未提交事务修改过的记录(脏读),因此直接读取版本链的最新版本就可以了;对于使用SERIALIZABLE隔离级别的事务来说,需要加锁的方式保证事务的串行化;对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,都必须保证事务1已经修改了记录单是尚未提交,事务2不能直接读取到最新版本的记录,即事务1中尚未提交的记录修改对事务2是不可见的。
为了保证READ COMMITTED和REPEATABLE READ隔离级别的事务,尚未提交的记录修改对其他事务不可见,InnoDB提出了ReadView的概念,ReadView主要由以下四部分组成:

  • m_ids:表示在生成ReadView时当前系统中“活跃”的读写事务的事务id列表,注意事务尚未提交时的状态为“活跃”状态;
  • min_trx_id :表示在生成ReadView时当前系统中活跃的(尚未提交的)读写事务中最小的事务id,也就是m_ids中的最小值;
  • max_trx_id :表示生成ReadView时系统中应该分配给下一个事务的id值;
  • creator_trx_id : 表示生成该ReadView的事务的事务id。

举例:现在有id为1,2,3这三个事务,之后id为3的事务提交了,一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。

如何根据某个读事务生成的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时生成该版本的事务已经被提交,该版本对当前事务可见。

开头介绍MVCC机制概述时提高,读事务在生成ReadView时,在READ COMMITTED和REPEATABLE READ隔离级别下生成的时机是不同的,通过READ COMMITTED隔离级别的事务在每次进行SELECT操作前都会生成1个ReadView,REPEATABLE READ隔离级别的事务只在第1次进行SELECT操作前生成1个ReadView,之后的查询操作都重复使用这个ReadView,下面结合例子具体介绍。

READ COMMITTED

比如现在系统里有两个事务id分别为100、200的事务在执行,记录初始时name值为“刘备”,如下:

#事务id为100的事务执行如下语句,注意还没有COMMIT,即事务id为100的事务处于"活跃"状态
BEGIN;
UPDATE hero SET name='关羽' WHERE number=1;
UPDATE hero SET name='张飞' WHERE number=1;
#事务id为200的事务在对其他表进行操作,目的是让该事务能够分配到一个事务id
BEGIN;

# 更新了一些别的表的记录
...

此刻,表hero中number为1的记录得到的版本链表如下所示:
在这里插入图片描述
假设现在有一个使用READ COMMITTED隔离级别的查询事务开始执行如下语句:

# 使用READ COMMITTED隔离级别的事务,事务id为0
BEGIN;

# SELECT1 : 事务100200未提交
# 得到的列name的值为'刘备'
select * from hero where number=1;

这个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的事务提交一下,就像这样:

# Transaction 100
BEGIN

update hero set name = '关羽' where number = 1;
update hero set name = '张飞' where number=1;

commit;

然后再到事务id为200的事务中更新一下表hero中number为1的记录,做如下UPDATE操作:

# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

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

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

此刻,表hero中number为1的记录的版本链就长这样:
在这里插入图片描述
然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个number为1的记录,如下:

# 使用READ COMMITTED隔离级别的事务
BEGIN;

#SELECT1 : 事务 100200均未提交
select * from hero where number=1; # 得到的列name的值为'刘备'

#SELECT2:事务 100提交,事务 200未提交
select * from hero where number =1; #得到的列name的值为'张飞'

这个SELECT2的执行过程如下:

  • 在执行SELECT语句时会又会单独生成一个ReadView,该ReadView的m_ids列表的内容就是200,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为’张飞’的记录。

从上面过程可以总结出:使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView,且在Read Committed隔离级别下,正是由于每次查询时事务都会生成一个最新的ReadView,这个ReadView太新了,导致每次查询出来的记录可能是不同的(比如select1查询出来的记录是"刘备",select2查询出来的记录是“张飞”),因此READ COMMITTED隔离级别可以避免脏读,但不能避免不可重复读。

REPEATABLE READ

比如现在系统里有两个事务id分别为100、200的事务在执行:

# 事务 100,尚未commit
update hero set name = '关羽' where number=1;
update hero set name = '张飞' where number =1;
# 事务200
BEGIN;

#更新了一些别的表的记录
...

此刻,表hero中number为1的记录得到的版本链如下所示:
在这里插入图片描述
现在有一个使用REPEATABLE READ隔离级别的事务开始执行查询:

#使用REPEATABLE READ隔离级别的事务执行select操作
BEGIN;

#SELECT1: 事务100200为提交
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的事务提交一下,就像这样:

# 事务100
BEGIN;

update hero set name='关羽' where number=1;
update hero set name='张飞' where number=1;

commit;

然后再到事务id为200的事务中更新一下表hero中number为1的记录:

# 事务 200
BEGIN;

#更新了一些别的表的记录
...

update hero set name='赵云' where number=1;

update hero set name='诸葛亮' where number=1;

此刻,表hero中number为1的记录的版本链就长这样:
在这里插入图片描述
然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个number为1的记录,如下:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;

#SELECT1: 事务100200均为提交
#得到的列name的值为'刘备'
select * from hero where number=1;

#SELECT2 : 事务100提交,事务200未提交
#得到的列name的值仍为'刘备'
select * from hero where numer=1;

这个SELECT2的执行过程如下:

  • 因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接用之前的ReadView,之前的ReadView的m_ids列表内的内容就是[100,200],min_trx_id为100,max_trx_ids为201,creator_trx_id为0;
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’诸葛亮’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性的要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’赵云’,该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本;
  • 下一个版本的列name的内容是’张飞’,该版本的trx_id值为100,而m_ids列表中是包含值为100的事务id的,所以该版本也不符合要求,继续跳到下一个版本;
  • 下一个版本的列name的内容是’关羽’,该版本的trx_id值为100,而m_ids列表中是包含值为100的事务id的,所以该版本也不符合要求,继续跳到下一个版本;
  • 下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为’刘备’的记录。

从上面过程可以总结出:使用REPEATABLE READ隔离级别的事务在查询时,仅会使用第一次select时生成的ReadView,相比READ COMMITTED隔离级别每次查询时都会生成一个ReadView,REPEATABLE READ隔离级别查询时使用的ReadView版本会没那么新,因此有些最新update并已经提交的事务对记录做的修改操作对查询事务就会不可见(避免了不可重复读现象的产生),因此REPEATABLE READ隔离级别可以同时避免脏读和不可重复读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/129094.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于LSTM三分类的文本情感分析,采用LSTM模型,训练一个能够识别文本postive, neutral, negative三种

基于LSTM三分类的文本情感分析,采用LSTM模型,训练一个能够识别文本postive, neutral, negative三种 ,含数据集可直接运行 完整代码下载地址:基于LSTM三分类的文本情感分析 基于LSTM三分类的文本情感分析 背景介绍 文本情感分析…

Comic Life - 超棒的漫画制作工具,拥有多种动画模版,创作属于自己的漫画

Comic Life - 超棒的漫画制作工具,拥有多种动画模版,创作属于自己的漫画 Comic Life是一个照片编辑器,能够添加各种效果,并基于它们创建漫画。该工具包包括各种各样的模板,可以很容易地将照片放置在工作表上&#xff0…

CKEditor 为你的Flask项目添加一个富文本编辑器

人家高高在上的CKEditor是有个官网的!! WYSIWYG HTML Editor with Collaborative Rich Text EditingRock-solid, Free WYSIWYG Editor with Collaborative Editing, 200 features, Full Documentation and Support. Trusted by 20k companies.https://c…

SBM模型分析全流程

数据包络分析DEA时,其研究投入产出效率情况,并且其假定投入和产出之间存在单调线性关系,其为一种线性规划技术来确定DMU相对效率的方法。但有时候会多出下‘非期望产出’,就是不希望有它产出,比如资金投入、教育投入换…

【Unity3D日常开发】Unity3D拓展开发:UI界面控制,UI界面的显示和隐藏实现

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客QQ群:1040082875 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 在开发中,可能遇到管理…

2022最后一天盘点

今天是今年最后的一天工作日,对于我来说就是今年的最后一天,因为放假了我就不需要思考了(当然公司后端程序员要保持24小时oncall) 1 阳完之后 还是有些 咳嗽,公司此起彼伏的咳嗽声,不知道什么时候所有人都…

21.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4] 示例 2: 输入:l1 [], l2 [] 输出:[…

【日常系列】LeetCode《23·回溯2》

数据规模->时间复杂度 <10^4 &#x1f62e;(n^2) <10^7:o(nlogn) <10^8:o(n) 10^8<:o(logn),o(1) 内容 lc 401 &#xff1a;二进制手表 https://leetcode.cn/problems/binary-watch/ 提示&#xff1a; 0 < turnedOn < 10 class Solution:def readBinary…

java jvm详解

java 系列文章之JVM 文章目录java 系列文章之JVM一、JVM1.基本概念2.运行过程二、线程三、JVM 内存区域1. 程序计数器(线程私有)2. 虚拟机栈(线程私有)3. 本地方法区(线程私有)4. 堆&#xff08;Heap-线程共享&#xff09;-运行时数据区5. 方法区/永久代&#xff08;线程共享&a…

UE4.27 C++调用DLL

1.首先在UE C项目中添加一个x64动态链接库&#xff0c;取名RouteInterface,放到Source下面&#xff1b; 2.在Source下面新建两个文件夹Include和Lisbs,这两个文件夹用来保存库的头文件和lib文件 3.在项目RouteInterface下面添加一个类&#xff1a;TestDll,并且把头文件保存到…

Mybatis-Plus查询投影与查询条件设置

目录 查询投影 查询指定字段 聚合查询 分组查询 查询条件设置 等值查询 范围查询 模糊查询 排序查询 查询投影 目前我们在查询数据的时候&#xff0c;什么都没有做默认就是查询表中所有字段的内容而查询投影即不查询所有字段&#xff0c;只查询出指定内容的数据 查询指…

九、文件File、IO流

文件File File可以用来表示计算机中的文件或者文件夹官方定义&#xff1a;文件和文件夹路径名的抽象表示形式 3种构造 1File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例2File(String parent, String child) 从父路径名字符串和子路径名字…

2022硅谷大厂的大!失!败!AiDA时尚设计师助手;2023热门IT技能预告;Uber送货机器人;GitHub今日热榜 | ShowMeAI资讯日报

&#x1f440;日报合辑 | &#x1f3a1;AI应用与工具大全 | &#x1f514;公众号资料下载 | &#x1f369;韩信子 &#x1f4e2; 『抖音』2022抖音热点数据报告&#xff0c;共度温暖岁末 抖音热点联合巨量算数&#xff0c;发布了《2022抖音热点数据报告》&#xff0c;盘点了20…

如何在pycharm上安装tensorflow

TensorFlow™是一个基于数据流编程&#xff08;dataflow programming&#xff09;的符号数学系统&#xff0c;被广泛应用于各类机器学习&#xff08;machine learning&#xff09;算法的编程实现&#xff0c;其前身是谷歌的神经网络算法库DistBelief 。 Tensorflow拥有多层级结…

qt 崩溃处理

Windows系统MSVC编译器的dump文件 debug模式 生成exe自带生成pdb文件&#xff0c;所以无需处理。 1.生成dump文件 通过修改注册表&#xff0c;增加注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps 具体见&#xff1a;利用vs 分…

Forrester Wave发布最新报告 腾讯云数据连接器评分卓越

全球权威研究机构 Forrester 在2022年12月8日最新发布的《中国公有云开发和基础设施平台&#xff0c;Q4 2022》报告中&#xff0c;腾讯云获得高分&#xff0c;位列“领导者象限”。Forrester在报告中提出&#xff1a;“企业在进行公有云开发和基设施平台提供商的选项中&#xf…

php宝塔搭建部署实战帮管客CRM客户管理系统源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套php开发的帮管客CRM客户管理系统源码&#xff0c;感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&#xf…

商业智能BI中,业务质量分析和业务成本分析

最初谁也没有想到&#xff0c;信息化 、数字化技术及其应用能够在如此短时间内快速覆盖了社会的方方面面&#xff0c;如今人们的衣食住行和工作生活娱乐都离不开数字化、数据的身影。 数据分析&#xff0c;是离不开业务的&#xff0c;只有把业务研究好了&#xff0c;所做出的报…

Numpy 数组切片

一、列表切片&#xff08;一维数组&#xff09; 1.1、切片原理 列表切片是从原始列表中提取列表的一部分的过程。在列表切片中&#xff0c;我们将根据所需内容&#xff08;如&#xff0c;从何处开始&#xff0c;结束以及增量进行切片&#xff09;剪切列表。Python中符合序列的…

【论文阅读】Online Decision Based Visual Tracking via Reinforcement Learning

Online Decision Based Visual Tracking via Reinforcement Learning 概述 本文2020年发布于NeurIPS(CCF-A)。视觉跟踪通常基于目标检测或者模板区配&#xff0c;但它们都只适用于特定的场景或对象。因为它们遵循不同的跟踪原则&#xff0c;直接将它们融合在一起是不明智的。…