数据库的MVCC机制

article/2025/9/15 13:04:47

MVCC

1. 什么是MVCC

MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版 本管理来实现数据库的 并发控制 。这项技术使得在InnoDB的事务隔离级别下执行 一致性读 操作有了保 证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样 在做查询的时候就不用等待另一个事务释放锁。

2. 快照读与当前读

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突 ,做到 即使有读写冲突时,也能做到 不加锁 , 非阻塞并发读 ,而这个读指的就是 快照读 , 而非 当前读当前 读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

2.1 快照读(MVCC解决幻读)

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

SELECT * FROM player WHERE ...

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下, 避免了加锁操作,降低了开销。

既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

2.2 当前读(临键锁解决幻读)

当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务 不能修改当前记录,会对读取的记录进行加锁。加锁的 SELECT,或者对数据进行增删改都会进行当前 读。比如:

在这里插入图片描述

3. 复习

3.1 再谈隔离级别

我们知道事务有 4 个隔离级别,可能存在三种并发问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHOUXP6d-1665711833544)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012104229350.png)]

在MysSQL中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,如果仅从定义的角度来看,它并不能解决幻读问题。如果我们想要解决幻读问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样一来就会大幅降低数据库的事务并发能力。

MVCC 可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题!它可以在大多数情况下替代行级锁,降低系统的开销。

另图:mysql的可重复读也解决了幻读,间隙锁能解决这个问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wP5J8bCj-1665711833545)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012104307015.png)]

3.2 隐藏字段、Undo Log版本链

回顾一下undo日志的版本链,对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必 要的隐藏列。

  • trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给 trx_id 隐藏列。
  • roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然 后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

共有三个隐藏字段(和本节没关系)

  • DB_ROW_ID: 如果没有为表显式的定义主键,并且表中也没有定义唯一索引,那么InnoDB会自动为表添加一个row_id的隐藏列作为主键。

举例说明:

假设插入该记录的事务id为8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tg6Mgg9K-1665711833546)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012155702591.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yfxgMWqe-1665711833546)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012155749665.png)]

每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个 roll_pointer 属性 ( INSERT 操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些 undo日志 都连起来,串成一个链表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehub7vgD-1665711833547)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012155816974.png)]

对该记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数 的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 版本链 ,版 本链的头节点就是当前记录最新的值。 每个版本中还包含生成该版本时对应的 事务id 。

4. MVCC实现原理之ReadView(ReadView和事务是一对一的)

MVCC 的实现依赖于:隐藏字段、Undo Log、Read View。

4.1 什么是ReadView

在MCC机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在Undo Log里如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到ReadView了,它帮我们解决了行的可见性问题。

ReadView就是某一个事务(一个ReadView和一个事务是一对一的)在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会==生成数据库系统当前的一个快照,==InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的ID(“活跃”指的就是,启动了但还没提交)。这样当前事务可以知道那些事务是活跃的。

4.2 设计思路

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

使用 SERIALIZABLE 隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。

上面两种隔离级别不需要MVCC

使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,==核心问题就是需要判断一下undo日志版本链中的哪个版本是当前事务可见的,==这是ReadView要解决的主要问题。

这个ReadView中主要包含4个比较重要的内容,分别如下:

  • creator_trx_id ,创建这个 Read View 的事务 ID。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sezWV6IF-1665711833548)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012221811073.png)]

  • trx_ids ,表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表 。

  • up_limit_id ,活跃的事务中最小的事务 ID。

  • low_limit_id ,表示生成ReadView时系统中(包括了那些已经提交了的事务)应该分配给下一个事务的 id 值。low_limit_id 是系统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-suV9Fpcu-1665711833548)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012221948464.png)]

4.3 ReadView的规则

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

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

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

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

  • 如果被访问版本的trx_id属性值在ReadView的up_limit_id和low_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中。

    • 如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
    • 如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

4.4 MVCC整体操作流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2NlC2cC-1665711833548)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012225154995.png)]

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

  1. 首先获取事务自己的版本号,也就是事务 ID;
  2. 获取 ReadView;
  3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 版本链中获取历史快照;
  5. 最后返回符合规则的数据。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次 Read View

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dLFrmf9s-1665711833549)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012230236058.png)]

注意,此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生 不可重复读或者幻读的情况。

当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会 获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View,如下表所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQTiyb7L-1665711833549)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012230318045.png)]

5、如何解决幻读

解决幻读是在 REPEATABLE READ 隔离级别

使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之 后的查询就不会重复生成了。

假设现在表 student 中只有一条数据,数据内容中,主键 id=1,隐藏的 trx_id=10,它的 undo log 如下图 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fU6f5qJ4-1665711833550)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012233324826.png)]

假设现在有事务 A 和事务 B 并发执行, 事务 A 的事务 id 为 20 , 事务 B 的事务 id 为 30 。

步骤1:事务 A 开始第一次查询数据,查询的 SQL 语句如下。

select * from student where id >= 1

在开始查询之前,MySQL 会为事务 A 产生一个 ReadView,此时 ReadView 的内容如下: trx_ids= [20,30] , up_limit_id=20 , low_limit_id=31 , creator_trx_id=20 。

由于此时表 student 中只有一条数据,且符合 where id>=1 条件,因此会查询出来。然后根据 ReadView 机制,发现该行数据的trx_id=10,小于事务 A 的 ReadView 里 up_limit_id,这表示这条数据是事务 A 开 启之前,其他事务就已经提交了的数据,因此事务 A 可以读取到。

步骤2:接着事务 B(trx_id=30),往表 student 中新插入两条数据,并提交事务。

insert into student(id,name) values(2,'李四');
insert into student(id,name) values(3,'王五');

此时表student 中就有三条数据了,对应的 undo 如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AjXvROpr-1665711833551)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\image-20221012234929778.png)]

**步骤3:**接着事务 A 开启第二次查询,根据可重复读隔离级别的规则,此时事务 A 并不会再重新生成 ReadView。此时表 student 中的 3 条数据都满足 where id>=1 的条件,因此会先查出来。然后根据 ReadView 机制,判断每条数据是不是都可以被事务 A 看到。

  1. 首先 id=1 的这条数据,前面已经说过了,可以被事务 A 看到。
  2. 然后是 id=2 的数据,它的 trx_id=30,此时事务 A 发现,这个值处于 up_limit_id 和 low_limit_id 之 间,因此还需要再判断 30 是否处于 trx_ids 数组内。由于事务 A 的 trx_ids=[20,30],因此在数组内,这表 示 id=2 的这条数据是与事务 A 在同一时刻启动的其他事务提交的,是正在活跃的事务,所以这条数据不能让事务 A 看到。
  3. 同理,id=3 的这条数据,trx_id 也为 30,因此也不能被事务 A 看见。

http://chatgpt.dhexx.cn/article/4oHqhA3q.shtml

相关文章

MySQL之MVCC机制

1. MVCC概念 MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。 在Mysql的InnoDB引擎中就是指在…

【MySQL进阶-05】深入理解mvcc机制(详解)

MySql系列整体栏目 内容链接地址【一】深入理解mysql索引本质https://blog.csdn.net/zhenghuishengq/article/details/121027025【二】深入理解mysql索引优化以及explain关键字https://blog.csdn.net/zhenghuishengq/article/details/124552080【三】深入理解mysql的索引分类&a…

MVCC及实现原理

!首先声明,MySQL 的测试环境是 5.7 前提概要 什么是 MVCC什么是当前读和快照读?当前读,快照读和 MVCC 的关系MVCC 实现原理 隐式字段undo日志Read View整体流程MVCC 相关问题 RR 是如何在 RC 级的基础上解决不可重复读的&#xf…

4gl程式debug常用技巧

1、带参数的D: ‘MISC’表示具体料号传入 2、指定行数设置断点 比如我要在程式axmt360中的211行开始D 进入Debug界面后按ctrlD 弹出界面 输入b 行数 点OK 出现上面表示已设置OK,然后点cancel或叉叉退出 最后点下面按扭,程式跑到210行时自…

【OpenGL】十五、OpenGL 绘制三角形 ( 绘制 GL_TRIANGLE_FAN 三角形扇 )

文章目录 一、绘制 GL_TRIANGLE_FAN 三角形1、绘制 3 个点的情况2、绘制 4 个点的情况3、绘制 5 个点的情况4、绘制 6 个点的情况 二、相关资源 一、绘制 GL_TRIANGLE_FAN 三角形 GL_TRIANGLE_FAN 的绘制规则是 , 以第 1 1 1 个点作为顶点 , 第 1 , 2 , 3 1,2,3 1,2,3 个点组…

Unity3D笔记十八 GL图像库

作者:PEPE 出处:http://pepe.cnblogs.com/ 1、绘制2D图像的时需要使用GL.LoadOrtho()方法来将图形映射到平面中。 2、所有绘制相关的内容都要写在OnPostRender()方法中。 3、有关GL图像库的脚本需要绑定到Hierarchy视图中Camera上,否则无法…

Mapbox GL插件之echartsLayer

Mapbox GL除了本身的api具有的功能以外,还能够集成各种开源的类库。 ECharts 是一个使用 JavaScript 实现的开源可视化库,涵盖各行业图表,百度公司开发的,同时其中也有一些地图的效果。 Mapbox GL的echarts插件,在gith…

【OpenGL】十八、OpenGL 绘制多边形 ( 绘制 GL_POLYGON 模式多边形 )

文章目录 一、绘制 GL_POLYGON 模式多边形二、多边形绘制顺序分析三、相关资源 一、绘制 GL_POLYGON 模式多边形 使用 glBegin(GL_POLYGON) 设置绘制多边形 , 不管有几个点 , 都按照指定的顺序连接起来 ; 注意 : 这些点组成的多边形必须是凸多边形 , 不能是凹多边形 ; 代码示例…

gl_FragCoord 的含义

gl_FragCoord 表示当前片元着色器处理的候选片元窗口相对坐标信息,是一个 vec4 类型的变量 (x, y, z, 1/w), 其中 x, y 是当前片元的窗口坐标,OpenGL 默认以窗口左下角为原点, 在 着色器中通过布局限定符可以重新设定原点&#xf…

WebGL着色器内置变量gl_PointSize、gl_Position、gl_FragColor、gl_FragCoord、gl_PointCoord

WebGL着色器内置变量 WebGL中文教程网 本文是WebGL教程(电子书)的2.7节内容 着色器语言在GPU的着色器单元执行,javascript语言、C语言在CPU上执行,任何一种语言的语法规则,整体设计都和它执行的硬件有一定的关系,GPU和CPU执行程…

Unity画线之GL

上一篇中,SetPixel的方法,卡顿严重,暂未解决,又去看了原来的GL画线,自己画图思考了一下适配UI的问题,最终解决。 特此说明,GL画线功能,及Shader均为借鉴,自己做了优化。…

GL823K

下面是另一家SD/TF解码芯片的方案 ![](https://img-blog.csdnimg.cn/20210319145313645.png?x-oss-processimage/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTU3MDgwNA,size_16,color_FFFFFF,t_70 13030533945 VX

GL各个表结构总结

NewProgramer EBS GL表结构学习(转) gl_code_combinations:科目组合 字段名 含义 备注 code_combination_id 主键,科目编码ID,自动编号 segment1 分行代码 setgment2 是受益部门 segment3 科目代码 segment4 产品…

初识mapbox GL

一、概述 最近由于项目的需求,借此机会对mapbox GL做了一个系统的学习,同时也对整个学习过程做一个记录,一方面留作自用,另一方面也希望看到此文的人在学习mapbox GL的时候,能够有所启发、有所收获。 二、快速认识 …

支持Genero BDL 4gl语言的编辑器

内测版本出来啦。点此下载: FglDeveloper v1.0 →此版本已下架 还有bug,分享几张截图 模板产生器: 画面产生器: 编辑器各种变量提醒 详细功能小伙伴们下载后体验。

TOPGP5.3:导入jar包并在4GL中引用

查看环境$CLASSPATH 上传引用到的JAR包到以下目录 /u1/topprod/tiptop/ds4gl2/bin/javaad/jar 以上为GP5.3目录,其他版本系统可根据查看到的$CLASSPATH上传到相应目录设置环境变量 GP5.3系统中,$CLASSPATH环境变量的设置存在下图文件中: …

4gl调用WEB API,实现JSON传递(Demo)

测试环境: GP5.25 , fjs版本2.32,解析json所需要的jar依赖包 (PS: 如果没有记错是fjs2.32版本及以上才支持java bridge,所以GP 5.25以下的同学就不要用这种方式去测试) 测试内容: 利用此fjs版本对java bridge的支持,实现4gl调用WEB API,实现json传递 测试步骤如下: 1.下载本…

给大家展示一下4gl编辑器

(正式版已发布点击下载)特地为编辑器开发内置语法解析器,将在代码编辑过程中实时提示代码错误,并且错误提示都是中文显示(楼主英文太垃圾只有做中文了),不再需要频繁上传服务器了哦, 经过楼主努力已经把所有的内置函数…

【实习之T100开发】Genero FGL (TIPTOP4GL) 学习笔记(1)

Genero FGL 学习 Genero FGL 简介Genero FGL 开发(编译、连接、执行)第一个程序 Hello World变量与运算符变量定义(DEFINE)预定义变量变量集合(RECORD )数据结构(TYPE)变量赋值&…

win10忘记密码重置密码,一行代码帮你解决

步骤如下: 1.右击windows图标,选择进入Windows PowerShell(管理员) 2.敲入代码net user 加上你的用户名和新密码,Ok,问题解决,你就可以用你的新密码登陆了