MySQL第一讲 一遍让你彻底掌握MVCC多版本并发控制机制原理

2023-11-17

        Mysql在可重复读隔离级别下,同样的sql查询语句在一个事务里多次执行查询结果相同,就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果。这个隔离性就是靠MVCC(Multi-Version Concurrency Control)机制来保证的,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥,而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。

查看隔离级别

 select @@tx_isolation;

查看MySQL事物的隔离级别报错:
mysql> select @@tx_isolation;
ERROR 1193 (HY000): Unknown system variable 'tx_isolation'
老版本MySQL叫tx_isolation
MySQL8叫transaction_isolation
 
select @@transaction_isolation;

select @@global.transaction_isolation; 

设置全局事务隔离级别

set global transaction isolation level repeatable read

设置当前session会话的

set session transaction isolation level repeatable read

undo日志版本链与read view机制详解

undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志。其中聚簇索引记录中都会包含下面这连个必要的隐藏列(row_id并不是必要的;在创建的表有主键时,或者有不允许为NULL的UNQIUE键试,都不会包含row_id列)

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

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

需要判断版本链中的哪个版本是是当前事务可见的,因此有了一致性视图的概念。其中有四个属性比较重要

  • m_ids: 在生成ReadView时,当前活跃的读写事务的事务id列表
  • min_trx_id:  在生成ReadView时,当前系统中活跃的读写事务中最小的事务Id,也就是m_ids中的最小值
  • max_trx_id: 在生成ReadView时,系统应该分配给下一个事务的事务id值(主要max_trx_id不是m_ids中的最大事务id值,事务id是递增分配的)
  • creator_trx_id: 生成该ReadView的事务的事务id(只有对表中的记录进行改动,即执行 insert,update,delete操作,才会为事务分配唯一的事务id,否则事务的事务id默认为0)

事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

版本链比对规则:

版本链中的当前版本是否可以被当前事务可见的要根据这四个属性按照以下几种情况来判断

  • 当 trx_id = creator_trx_id 时:当前事务可以看见自己所修改的数据, 可见
  • 当 trx_id < min_trx_id 时 : 生成此数据的事务已经在生成readView前提交了, 可见
  • 当 trx_id >= max_trx_id 时 :表明生成该数据的事务是在生成ReadView后才开启的, 不可见
  • 当 trx_id  在(min_trx_id ,max_trx_id)内 时

    • trx_id 在 m_ids 列表里面 :生成ReadView时,活跃事务还未提交,不可见
    • trx_id 不在 m_ids 列表里面 :事务在生成readView前已经提交了,可见

如果某一个版本的数据对当前事务不可见,就顺着版本链往下找下一个版本的数据,并继续使用上面的方法判断记录的可见性,直到版本链中的最后一个版本。

对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数据。

注意:

  • 读提交(read committed RC) 是在每一次读取数据的时候生成ReadView的
  • 可重复读(repeatable read RR)是在第一次读取数据的时候生成ReadView的

总结:

MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。MVCC使得数据库读不会对数据加锁,提高了数据库的并发处理能力。借助MVCC,数据库可以实现READ COMMITTED,REPEATABLE READ等隔离级别,用户可以查看当前数据的前一个或者前几个历史版本,保证了ACID中的I特性(隔离性)。

下面结合实例分析在可重复读以及读已提交隔离级别下的分析当前事务读取的数据

  • 假设数据库中有一条初始数据 姓名是小杰,id是1 (id,姓名,trx_id,roll_point),插入此数据的事务id是1
  • 尤其要指出的是,只有这个事务操作了某些表的数据后当更改操作发生的时候(update,delete,insert),才会分配唯一的事务id,并且此事务id是递增的,单纯开启事务是没有事务id的,默认为0
  • T1时刻 事务100 更新name=A
  • T2时刻 事务100更新name=B
  • T3时刻事务200更新name=C

同颜色代表是同一事务内的操作

分析一下此时T4时刻事务300要读了,究竟会读到什么数据

此时 (生成一致性视图ReadView

  • m_ids 是[100,200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 100
  • max_trx_id 是 201
  • creator_trx_id 为 0

此时版本链最上面一条记录trx_id(事务id)是 200,在m_ids列表内,因此不可见,根据roll_pointer跳到下一个版本,事务trx_id=100,同样在m_ids列表内,不可见,再根据roll_ponter跳到下一版本,事务trx_id=1, 符合txr_id<min_trx_id, 可见。所以读取的数据就是小杰

分析完第一个读,我们继续向下分析

  1. 当T5时刻时,事务100提交
  2. 当T6时刻时,事务300将名字改为D
  3. 当T7时刻时,事务400读取当前数据

此时这条数据的版本链如下

此时 (重新生成一致性视图ReadView

  • m_ids 是[200,300]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 200
  • max_trx_id 是 301
  • creator_trx_id 为 0

此时版本链最上面一条记录trx_id(事务id)是 300,活跃事务列表为[200,300],所以事务300数据不可见,同理事务200数据不可见,事务100在(min_trx_id,max_trx_id)内且不在活跃事务列表[200,300]中,表示此数据可见,所以读取出来的数据就是B

分析完第二个读,我们继续向下分析

  1. 当T8时刻时,事务200将名字改为E
  2. 当T9时刻时,事务200提交
  3. 当T10时刻时,事务300读取当前数据

此时这条数据的版本链如下

此时 (重新生成一致性视图ReadView

  • m_ids 是[300]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 300
  • max_trx_id 是 301
  • creator_trx_id 为 300

此时版本链最上面一条记录trx_id(事务id)是 200,在(min_trx_id,max_trx_id)内,且不在活跃事务列表 ,代表生成这个数据的事务已经在生成ReadView前提交了,此数据可见,所以读出的数据就是E.

当隔离级别是读已提交RC的情况下,每次读都会重新生成 一致性视图(ReadView)

  • T4时刻 事务300读取到的数据是小杰
  • T7时刻 事务400读取到的数据是B
  • T10时刻 事务300读取到的数据是E

那么在可重复读隔离级别下,上面那些读的数据会是怎么一回事

  • 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的

在此可重复读RR隔离级别下,

1:T4时刻时事务300第一次读时 read-view是

  • m_ids 是[100,200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 100
  • max_trx_id 是 201
  • creator_trx_id 为 0

很显然T4时刻读取的数据是和上面读已提交隔离级别下T4时刻读取的数据一样,也是小杰

2:T7时刻事务400读取据 时 read-view是

  • m_ids 是[200,300]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 200
  • max_trx_id 是 301
  • creator_trx_id 为 0

很显然T4时刻读取的数据是和上面读已提交隔离级别下T7时刻读取的数据一样,也是B

3:T10时刻事务300读取数据时,read-view就和上面读已提交隔离级别的read-view不一样了,因为可重复读隔离级别下事务开启后的read读会和第一次读的read-view视图保持一致,所以T10时刻的read-view视图一样是

  • m_ids 是[100,200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 100
  • max_trx_id 是 201
  • creator_trx_id 为 300

而位于版本链最上面的记录的trx_id(事务id)是 200,数据是E,事务200在活跃事务列表 ,所以数据不可见,根据回滚指针找到上个版本,发现事务id是300,当前事务也是creator_trx_id =trx_id = 300,可见,所以读取的数据是D

当隔离级别是可重复读RR的情况下,事务只在第一次读的时候生成Read-View

  • T4时刻 事务300读取到的数据是小杰
  • T7时刻 事务400读取到的数据是B
  • T10时刻 事务300读取到的数据是D

 再继续分析如果没有事务300这条更改的这条记录,又该怎么继续向下分析呢?

  • m_ids 是[100,200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 100
  • max_trx_id 是 201
  • creator_trx_id 为 300

而位于版本链最上面的记录的trx_id(事务id)是 200,数据是E,符合min_trx_id<=trx_id<max_trx_id ,但是事务200在活跃事务列表 ,所以数据不可见,根据回滚指针找到上个版本,发现事务id还是200继续查找上一个版本是100,100也在当前活跃列表里面,所以不可见根据回滚指针找上一个版本知道找到事务1,满足trx_id<min_trx_id,是可读数据所以读取的数据是小杰

我们再简单分析一个例子:

数据操作版本链如下:

 最开始事务假设为80,id=1的name为lilei,之后启动了事务100,事务200,事务300

T1时候 事务100修改操作id=2的数据

T2时候 事务200修改操作id=3的数据

T3时候 事务300修改操作id=1的数据 设置name=lilei300,并提交事务

T4时刻 事务400开始读id=1的数据,这时的read-view视图如下

  • m_ids 是[100,200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 100
  • max_trx_id 是 401
  • creator_trx_id 为 0

这时位于版本链最上面的记录的trx_id(事务id)是 300,在(min_trx_id,max_trx_id)内,但是事务300不在活跃事务列表 ,表示事务已经提交是可见的

T5时刻,事务100修改操作id=1的数据 设置name=lilei1

T6时刻,事务100修改操作id=1的数据 设置name=lilei2

T7时刻 ,事务400开始读id=1的数据,这时如果是RR隔离级别

可得read-view视图如下:

  • m_ids 是[100,200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 100
  • max_trx_id 是 401
  • creator_trx_id 为 0

这时位于版本链最上面的记录的trx_id(事务id)是 100,事务100在活跃列表中,表示数据不可见,继续查找版本链,发现事务300不在活跃事务列表 ,表示事务已经提交是可见的

T8时刻 事务100 提交事务 事务200修改操作id=1的数据,设置name=lilei3

T9时刻 事务200修改操作Id=1的数据,设置name=lilei4

T10时刻 事务400开始读id=1的数据,这时如果是RR隔离级别

可得read-view视图如下:

  • m_ids 是[100,200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 100
  • max_trx_id 是 401
  • creator_trx_id 为 0

这时位于版本链最上面的记录的trx_id(事务id)是 200,事务200在活跃列表中,表示数据不可见,继续查找版本链,发现事务300不在活跃事务列表 ,表示事务已经提交是可见的,读取数据为lilei300

在RC隔离级别下 可得read-view视图如下:

  • m_ids 是[200]: 当前活跃的读写事务的事务id列表
  • min_trx_id 是 200
  • max_trx_id 是 401
  • creator_trx_id 为 0

这时位于版本链最上面的记录的trx_id(事务id)是 200,事务200在活跃列表中,表示数据不可见,继续查找版本链,发现trx_id=100<min_trx_id ,表示事务已经提交是可见的,读取数据为lilei2

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL第一讲 一遍让你彻底掌握MVCC多版本并发控制机制原理 的相关文章

随机推荐

  • k8s--基础--23.1--认证-授权-准入控制--介绍

    k8s 基础 23 1 认证 授权 准入控制 介绍 1 介绍 k8s对我们整个系统的认证 授权 访问控制做了精密的设置 对于k8s集群来说 apiserver是整个就集群访问控制的唯一入口 我们在k8s集群之上部署应用程序的时候 可以通过宿
  • 数据结构_课程设计——最小生成树:室内布线

    转载请注明出处 http blog csdn net lttree 这道课程设计 费不少时间 太麻烦了 明明是能力不够 最小生成树 室内布线 题目要求 装修新房子是一项颇为复杂的project 如今须要写个程序帮助房主设计室内电线的布局 首
  • 数组练习题(编程题)

    1 从终端 键盘 读 20个数据到数组中 统计其中正数的个数 并计算这些正数之和 int sum 0 int count 0 int input int arr 20 0 初始化处理 arr 0x0000002d1b13f8c0 85899
  • 7.25总结,正则表达式+号的含义

    一 正则表达式 由 括起来的是需要判断的字符 eg a z A Z 0 9 在 加 号表示多次并且连续满足 条件的式子 表示有没有 String s1 123qwe13qwe s1 s1 replaceAll 0 9 替换 System o
  • 用Python爬虫接私活,赚了32K!

    网络爬虫 很多人觉得这是技术控的专属 实际上爬虫是人人都能掌握的技能 爬虫到底能干什么 基本你所能看到的全部信息 它都能抓取 例如 收集并批量下载某音乐软件付费歌曲 某视频软件的付费视频 采集北京所有小区的信息及北京所有小区的所有历史成交记
  • centos7 lvm 创建脚本

    centos7 lvm 创建脚本 Centos7 lvm创建 适用场景只有一块新加的磁盘 且未分区 挂载目录为riva 可自定义 date 2023 bin bash 注意此处变量 Disk dev sd 不同的平台会有差异 比如腾讯云为
  • Attempted to load tokenizers/punkt/PY3/english.pickle

    分明已经把punkt放到服务器相应文件下 但是还是显示没成功 错误原因是解压得时候文件目录有两个punkt
  • VUE之自定义插件

    index js文件 import promptBox from prompt box vue 定义插件对象 const PromptBox vue的install方法 用于定义vue插件 PromptBox install functio
  • rocketMq介绍和安装

    rocketMq介绍和安装 Mq介绍 MQ MessageQueue 消息队列 队列 是一种FIFO 先进先出的数据结构 消息由生产者发送到MQ进行排队 然后按原来的顺序交由消息的消费者进行处理 QQ和微信就是典型的MQ MQ的作用 主要有
  • Vue项目中如何引入外部js文件,并使用其中定义的函数

    Vue项目中如何引入外部js文件 并使用其中定义的函数 一些常用的功能函数 我们希望将其封装起来放入一个外部JS文件中 好方便我们在需要的时候使用 vue可以使用import指令引入外部文件 但是作为新手 在使用过程中难免会导致很多错误 这
  • maven导入依赖失败问题——最系统、最彻底的解决方案

    目录 一 idea配置maven 1 配置maven版本及本地仓库 二 清理Local Repository的 lastUpdated文件 三 在idea中重新导入依赖 一 idea配置maven 1 配置maven版本及本地仓库 关于项目
  • 排序算法之堆排序(Heap Sort)——C语言实现

    堆排序 Heapsort 是指利用堆积树 堆 这种数据结构所设计的一种排序算法 它是选择排序的一种 算法分析 在学习堆排序之前我们要先了解堆这种数据结构 堆的定义如下 n个元素的序列 k1 k2 kn 当且满足以下关系时 称之为堆 若将此序
  • 短视频平台-小说推文(知乎)推广任务详情

    知乎会员 知乎日结内测中 可能暂只对部分优质会员开放 2023 03 29通知 知乎拉新项目 由于内部测试转化较低 暂时下线 原有关键词出单不受影响 1 关键词 1 1 选择会员文 在知乎 首页 或者 会员 里面选取 需要选取文章链接 点进
  • 二.centos7和主机实现xftp传文件,包含解决一些主机虚拟机互联的问题

    首先打开安装好的centos7 一 需要获取的信息 虚拟机 ifconfig 信息 主要是ensxx的信息 主机 ipconfig 信息 主要是vm8的信息 虚拟机点 编辑 虚拟网络编辑器 界面 查看虚拟机右下是否连接 点虚拟机右上角 看有
  • Nacos启动: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorExceptionTable nacos_config.config_inf

    把nacos 1 1 4 server下载到本地之后 然后直接在bin目录下启动startup cmd报错 本地Mysql版本 5 6 44 nacos server版本 1 1 4 我找了很多解决办法 更多的说的是nacos自带的mysq
  • vue 实时往数组里追加数据

    使用Vue set 以下来解读一下 Vue set this tableDatas this selected obj 1 this tableDatas是我们声明好的数组 以下是自定义数据 tableDatas id 1 caseName
  • Python之数据分析(三维立体图像、极坐标系、半对数坐标)

    文章目录 写在前面 一 三维立体图像 1 三维线框 2 三维曲面 3 三维散点 二 极坐标系 三 半对数坐标 写在前面 import numpy as np import matplotlib pylab as mp 因此文章中的np就代表
  • Linux学习大纲

  • 以太网设计FAQ:以太网MAC和PHY

    问 如何实现单片 以太网 微控制器 答 诀窍是将微控制器 以太网媒体接入控制器 MAC 和物理接口收发器 PHY 整合进同一芯片 这样能去掉许多外接元器件 这种方案可使MAC和PHY实现很好的匹配 同时还可减小引脚数 缩小芯片面积 单片以太
  • MySQL第一讲 一遍让你彻底掌握MVCC多版本并发控制机制原理

    Mysql在可重复读隔离级别下 同样的sql查询语句在一个事务里多次执行查询结果相同 就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果 这个隔离性就是靠MVCC Multi Version Concurrency Contro