mysql mvcc 实例说明_Mysql MVCC

article/2025/11/9 23:27:08

一、MVCC概述

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。整个MVCC多并发控制的目的就是为了实现读-写冲突不加锁,提高并发读写性能,而这个读指的就是快照度, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。

当前读

读取的是记录数据的最新版本,并且当前读返回的记录都会加上锁,保证其他事务不会再并发的修改这条记录

快照读

读取的是记录数据的可见版本(可能是过期的数据),不用加锁。

总结来说MVCC的好处:

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能

同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

MVCC解决读写冲突,悲观锁或者乐观锁解决写写冲突

二、mvcc原理

MVCC的目的就是多版本并发控制,目的是为了解决读写冲突,总的来说MVCC通过保存数据在某个时间点的快照来实现的,意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的(即多版本)。如下示例:事务1和事务3可读到不同的数据快照。

时间点

事务1

事务2

事务3

T1

开始事务

开始事务

开始事务

T2

查询A的账户,金额为100

T3

修改A的账户,金额从100改为200

T4

提交事务

T5

查询A的账户,金额为100

查询A的账户,金额为200

MVCC最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB实现MVCC,多个版本的数据可以共存,它的实现原理主要是依赖记录中的 3个隐式字段、undo日志 和Read View 来实现的。

1、隐藏字段

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

DB_TRX_ID

6byte,最近操作(修改/插入)事务ID:记录创建这条记录或者最后一次修改该记录的事务ID

DB_ROLL_PTR

7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)

DB_ROW_ID

6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引

549e0d43ca72b6530dc80524d7f6f86b.png

如上图,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本。

2、undo日志

undo log主要分为两种:

insert undo log

代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

update undo log

事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

因此,对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:

比如事务0插入person表一条新记录,name为Jerry, age为24,隐式主键是1,事务ID和回滚指针,我们假设为NULL,如下图

ea465fb50f542414483506c132b18186.png

现在来了一个事务1对该记录的name做出了修改,改为Tom

在事务1修改该行数据时,数据库会先对该行加排他锁

然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本

拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它

事务提交后,释放锁

1f32f8bce9818fb2c68a1893b157e346.png

又来了个事务2修改person表的同一个记录,将age修改为30岁

在事务2修改该行数据时,数据库也先为该行加锁

然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面

修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录

事务提交,释放锁

213c15e4a980129f4b137c2789370d83.png

从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)

3、Read View(读视图)

Read View就是事务进行快照读操作的时候生产的读视图(Read View),记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大),所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。

我先简化一下Read View,我们可以把Read View简单的理解成有三个全局属性

rw_trx_ids

一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID

up_limit_id

记录rw_trx_ids列表中事务ID最小的ID

low_limit_id

ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1

判断逻辑如下:

首先比较DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到DB_TRX_ID 所在的记录,如果大于等于进入下一个判断

接下来判断 DB_TRX_ID 大于等于 low_limit_id , 如果大于等于则代表DB_TRX_ID 所在的记录在Read View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断

判断DB_TRX_ID 是否在活跃事务之中,rw_trx_ids.contains(DB_TRX_ID),如果在,则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在Read View生成之前就已经Commit了,你修改的结果,我当前事务是能看见的

四、整体流程

我们在了解了隐式字段,undo log, 以及Read View的概念之后,就可以来看看MVCC实现的整体流程是怎么样了。

当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务ID为2,此时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1,3的ID,维护在一个列表rw_trx_ids上

事务1事务2事务3事务4

事务开始

事务开始

事务开始

事务开始

修改且已提交

进行中

快照读

进行中

Read View不仅仅会通过一个列表rw_trx_ids来维护事务2执行快照读那刻系统正活跃的事务ID,还会有两个属性up_limit_id(记录rw_trx_ids列表中事务ID最小的ID),low_limit_id(记录rw_trx_ids列表中事务ID最大的ID,也有人说快照读那刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值+1,我更倾向于后者;所以在这里例子中up_limit_id就是1,low_limit_id就是4 + 1 = 5,rw_trx_ids集合的值是1,3。

我们的例子中,只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务,所以当前该行数据的undo log如下图所示;我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟up_limit_id,low_limit_id和rw_trx_ids进行比较,判断当前事务2能看到该记录的版本是哪个。

196900c477016f55725bd815d9cfea02.png

所以先拿该记录DB_TRX_ID字段记录的事务ID4去跟Read View的的up_limit_id比较,看4是否小于up_limit_id(1),所以不符合条件,继续判断 4 是否大于等于 low_limit_id(5),也不符合条件,最后判断4是否处于rw_trx_ids中的活跃事务, 最后发现事务ID为4的事务不在当前活跃事务列表中, 符合可见性条件,所以事务4修改后提交的最新结果对事务2快照读时是可见的,所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

也正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

五、mvcc示例解析

下面以RR隔离级别为例,结合前文提到的几个问题分别说明。

(1)脏读

时间点

事务1

事务2

T1

开始事务

开始事务

T2

修改A的金额,将金额从100改为200

T3

查询A的金额,为100,避免了脏读

T4

提交事务

当事务1在T3时刻读取A的余额前,会生成ReadView,由于此时事务2没有提交仍然活跃,因此其事务id一定在ReadView的rw_trx_ids中,因此根据前面介绍的规则,事务B的修改对ReadView不可见。接下来,事务A根据指针指向的undo log查询上一版本的数据,得到A的余额为100。这样事务1就避免了脏读。

(2)不可重复读

时间点

事务1

事务2

T1

开始事务

开始事务

T2

快照读A账户,为100

T3

修改A的金额,将金额从100改为200

T4

提交事务

T5

快照读A的金额,为100,避免了不可重复读

当事务1在T2时刻读取A的金额前,会生成ReadView。此时事务2分两种情况讨论,一种是如图中所示,事务已经开始但没有提交,此时其事务id在ReadView的rw_trx_ids中;一种是事务2还没有开始,此时其事务id大于等于ReadView的low_limit_id。无论是哪种情况,根据前面介绍的规则,事务2的修改对ReadView都不可见。当事务1在T5时刻再次读取A的余额时,会根据T2时刻生成的ReadView对数据的可见性进行判断,从而判断出事务2的修改不可见;因此事务1根据指针指向的undo log查询上一版本的数据,得到A的余额为100,从而避免了不可重复读。

(3)幻读

时间点

事务1

事务2

T1

开始事务

开始事务

T2

查询0

A:100(id=1)

T3

账户表中插入新用户

id=2

T4

提交事务

T5

查询0

A:100(id=1)避免了幻读

MVCC避免幻读的机制与避免不可重复读非常类似。当事务1在T2时刻读取0

六、RC,RR级别下的InnoDB快照读有什么不同?

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;

即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见

而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

七、总结

这里介绍了MVCC的原理,而且结合事务的隔离级别RR进行了说明。总的来说,InnoDB实现的RR,通过锁机制、MVCC等,实现了一定程度的隔离性,避免了脏读、不可重复读、幻读、更新覆盖问题。不过需要说明的是,RR虽然避免了幻读问题,但是毕竟不是Serializable,不能保证完全的隔离,比如如果在事务中第一次读取采用非加锁读,第二次读取采用加锁读,则如果在两次读取之间数据发生了变化,两次读取到的结果不一样,因为加锁读时不会采用MVCC。

八、参考资料


http://chatgpt.dhexx.cn/article/9VZvy2tk.shtml

相关文章

MVCC

一、什么是MVCC MVCC&#xff08;Multiversion concurrency control &#xff09;是一种多版本并发控制机制。 二、MVCC是为了解决什么问题? 并发访问(读或写)数据库时&#xff0c;对正在事务内处理的数据做多版本的管理。以达到用来避免写操作的堵塞&#xff0c;从而引发读操…

MVCC详解

一、前言 全称Multi-Version Concurrency Control&#xff0c;即多版本并发控制&#xff0c;主要是为了提高数据库的并发性能。以下文章都是围绕InnoDB引擎来讲&#xff0c;因为myIsam不支持事务。 同一行数据平时发生读写请求时&#xff0c;会上锁阻塞住。但mvcc用更好的方式…

MVCC 机制的原理及实现

什么是 MVCC MVCC (Multiversion Concurrency Control) 中文全程叫多版本并发控制&#xff0c;是现代数据库&#xff08;包括 MySQL、Oracle、PostgreSQL 等&#xff09;引擎实现中常用的处理读写冲突的手段&#xff0c;目的在于提高数据库高并发场景下的吞吐性能。 如此一来…

深入浅出:MVCC详解

什么是MVCC&#xff1a; MVCC(Multi Version Concurrency Control的简称)&#xff0c;代表多版本并发控制。与MVCC相对的&#xff0c;是基于锁的并发控制&#xff0c;Lock-Based Concurrency Control)。 MVCC最大的优势&#xff1a;读不加锁&#xff0c;读写不冲突。在读多写少…

什么是MVCC?MVCC解决了什么问题?MVCC的实现原理?

1.什么是MVCC&#xff1f; MVCC全称是【Multi-Version ConCurrency Control】&#xff0c;即多版本控制协议。 多版本控制&#xff08;Multiversion Concurrency Control&#xff09;: 指的是一种提高并发的技术。最早的数据库系统&#xff0c;只有读读之间可以并发&#xff…

MVCC详解,深入浅出简单易懂

一、什么是MVCC&#xff1f; mvcc&#xff0c;也就是多版本并发控制&#xff0c;是为了在读取数据时不加锁来提高读取效率和并发性的一种手段。 数据库并发有以下几种场景&#xff1a; 读-读&#xff1a;不存在任何问题。读-写&#xff1a;有线程安全问题&#xff0c;可能出…

【MySQL笔记】正确的理解MySQL的MVCC及实现原理

MVCC多版本并发控制 如果觉得对你有帮助&#xff0c;能否点个赞或关个注&#xff0c;以示鼓励笔者呢&#xff1f;&#xff01;博客目录 | 先点这里 &#xff01;首先声明&#xff0c;MySQL 的测试环境是 5.7 前提概要 什么是 MVCC什么是当前读和快照读&#xff1f;当前读&…

Oracle自定义函数

使用Navicat的话&#xff0c;可以点击函数&#xff0c;新建函数&#xff0c;根据引导完成一个函数的基本搭建。 语法和Java类似&#xff0c;其中对于变量赋值要使用 : 进行赋值。 具体语法可以参考一下 Oracle 自定义函数语法与实例_桑汤奈伊伏的博客-CSDN博客_oracle 自定义函…

Oracle 创建函数

Oracle创建函数是通过PL/SQL自定义编写的&#xff0c;通过关键字function按照自己的需求把复杂的业务逻辑封装进PL/SQL函数中&#xff0c;函数提供一个返回值&#xff0c;返回给使用者。这样使用者就不需要去理解业务逻辑&#xff0c;把PL/SQL函数中的业务逻辑交给专门的开发人…

Oracle函数的使用

在进行select查询的时候&#xff0c;可以为列指定函数&#xff0c;函数是sql语句中的一个非常有用的特性&#xff0c;oracle内置了用于处理字符&#xff0c;数字&#xff0c;日期及转换的各种函数&#xff0c;使用函数能够执行数据计算&#xff0c;修改列数据的显示&#xff0c…

Oracle函数【详细 包括举例】

概述 Oracle SQL 提供了用于执行特定操作的专用函数。这些函数大大增强了 SQL 语言的功能。函数可以接受零个或者多个输入参数&#xff0c;并返回一个输出结果。 Oracle 数据库中主要使用两种类型的函数&#xff1a; 1. 单行函数&#xff1a;对每一个函数应用在表的记录中时&a…

Oracle 函数编写

CREATE OR REPLACE FUNCTION f_homestay_count (wkt_poly CLOB ) RETURN NUMBER IS result NUMBER ; BEGINSELECTCOUNT (*) INTO resultFROMHOMESTAY_BASIC TWHEREsdo_anyinteract (T .geom_point,sdo_geometry (wkt_poly, 4326)) TRUE; RETURN (result) ;END ;因为之前都是…

Oracle 自定义函数

语法结构 CREATE [OR REPLACE] FUNCTION 定义的函数名称(参数名1 参数类型,参数名2 参数类型, ...) RETURN 返回值类型 AS/IS 返回值形参 形参类型实例化 BEGIN 方法体 &#xff08;其中用到if判断的话&#xff0c;每一个if对应一个end if&#xff0c;出现几次if就会有几个end…

oracle常用函数

1.sign sign函数是根据给的数为正数&#xff0c;就返回1,0返回0&#xff0c;负数返回-1。需要注意sign&#xff08;这个括号里面只能是个字段&#xff09;&#xff0c;在括号中写个子查询直接就报错了&#xff01; 2.decode 用法&#xff1a; decode(条件,值1,返回值1,值2,返回…

Oracle(四)Oracle 函数

目录 函数介绍Oracle字符型函数Oracle日期型函数系统日期、时间函数&#xff1a;数据库时区函数&#xff1a;给日期加上指定的月份函数&#xff1a;月份最后一天函数:指定日期后一周的日期函数:返回指定日期中特定部分的函数&#xff1a;返回两个日期间的月份数&#xff1a;日期…

oracle函数instr函数

1 instr函数的概念 在Oracle中可以使用instr函数对某个字符串进行判断&#xff0c;判断其是否含有指定的字符。在一个字符串中查找指定的字符,返回被查找到的指定 的字符的位置。 2 语法 instr(sourceString,destString,start,appearPosition) instr&#xff08;‘源字符串’,‘…

Oracle常用函数【建议收藏】

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;目前从事DBA及程序编程 &#xff08;Web\java\Python&#xff09;工作&#xff0c;主要服务于生产制造 现拥有 Oracle 11g OCP/OCM、 Mysql、Oceanbase&#xff08;OBCA&#xff09;认证 分布式TBase\TDSQL数据库、国…

Oracle常用函数大全

说明&#xff1a;新文章地址转为 Oracle数据库函数大全_長安社-王于铭.YuMing的博客-CSDN博客https://hevnchin.blog.csdn.net/article/details/132054755 MySQL数据库系统函数大全_長安社-王于铭.YuMing的博客-CSDN博客year&#xff1a;年份、month&#xff1a;月份、day&am…

Oracle中的函数(详细!!!)

文章目录 前言一、SQL中的函数两种SQL函数单行函数单行函数的分类1. 字符型函数LOWER函数UPPER函数INITCAP函数CONCAT函数SUBSTR函数INSTR函数LPAD|RPAD函数REPLACE函数 2. 数字函数ROUND函数TRUNC函数MOD函数 3. 使用日期查看系统时间根据时间查询信息日期的运算MONTHS_BETWEE…

OpenDaylight通过netconf对接netopeer2

目的 利用OpenDaylight(client)的南向接口netconf对接netopeer2(server) 搭建netopeer2 启动Ubuntu20的docker&#xff0c;将内部830端口映射为主机的22830端口 adminubuntu20:~$ docker run -d --name netopeer2_server --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro…