您现在的位置是:首页 > 正文

MySQL可重复读和读已提交实现原理,深入理解MVCC。

2024-04-01 04:41:03阅读 3

1.隔离级别

图片

MySQL中隔离级别分为4种,提未交读、读已提交、可重复读、串行化。同时MySQL默认隔离级别为可重复读。

图片
在这里插入图片描述

查看MySQL隔离级别

SELECT @@tx_isolation

设置当前会话隔离级别

set session transaction isolation level 隔离级别

2.脏读、不可重复读、幻读

建表语句如下

CREATE TABLE `account` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '姓名',
`balance` BIGINT ( 10 ) DEFAULT NULL COMMENT '余额',
PRIMARY KEY ( `id` ) 
) ENGINE = INNODB AUTO_INCREMENT = 3 DEFAULT CHARSET = utf8;

表数据如下

图片

脏读

所谓脏读就是指事务A对数据进行了修改但是还没有提交,此时事务B就能够查询到未提交的事务,同时对数据可以进行操作。

脏读存在于读未提交中,所以需要设置隔离级别为读未提交。如下所示,诸葛亮在事务A中扣款10000元,但是还没有提交,此时事务B就能够查询到扣款后的数据。但是如果此时A发生回滚会导致事务B的数据不是和之前查询的不一致,也就是脏读。

图片

不可重读

所谓不可重复读是指事务A查询到数据后,事务B做了修改后进行提交,此时事务A再此查询数据时发现和前一次的数据不一致。

脏读存在于读未提交中和读已提交,所以需要设置隔离级别为读未提交或读已提交。如下所示,事务A查询余额为10000元,然后事务B在T4时刻将诸葛亮余额扣款10000元,并在T5时刻进行事务提交,此时事务A在T6时刻查询余额为0元,可以看到事务A在T3时刻和T6时刻查询同一数据却得到了不同结果,这种情况称之为不可重复读。

图片

幻读

幻读是指事务A查询范围数据,此时事务B进行数据插入,然后事务A再此查询的时候发现数据多了一条,此时就像是产生了幻觉一样,所以称之为幻读。但是这种情况下的幻读在MySQL的可重复读情况下是不存在的,已经通过MVCC解决了。

我们可以通过以下方式来实现在可重复读情况产生的幻读。事务A先查询id为3的数据,由于没有此时为空,事务B插入一条id为3的数据,然后并提交事务,此时事务A再此插入id为3的数据会出现主键冲突。可以看到和之前看到的数据不一致,这种情况称之为幻读。这种情况产生幻读的原因是当前读(下面会介绍)。

图片

3.MVCC版本控制

如果表中数据如如下所示,同时隔离级别为可重复读那么按照下面的时间进行执行,此时你觉得事务A和事务B查询的结果会是什么呢?

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

在这里插入图片描述

答案是在事务B中查询的结果为3,而事务A中为1,或许很好理解事务A的值为什么是1,但是却并不好理解事务B为什么是3,这要从MySQL的MVCC开始说起

版本控制链

首先在Innodb中每一个事务都有一个事务ID,只要事务启动就会存在一个事务ID,叫作 transaction
id。而且事务ID是按照一定规律进行递增的,当我们对某一行数据进行更新操作时实际上会将当前的事务ID,做一个记录,这个记录存在于MySQL的隐藏列中,也就是row
trx_id。同时会产生一个undo log的指针来指向上一次的数据。

例如现在将表中id为1的数据的k修改为2,且当前事务ID为99,同时在版本控制链中,上一次这一行的数据是被事务id为98的进行插入的,那么这一行数据实际上的修改过程如下。

图片

可以看到,当执行更新操作后,实际上会在版本控制链中进行一个记录,可以理解为将原来的数据进行拷贝一份,同时现在用row_trx_id(6tyte)记录当前事务id,同时用DB_ROLL_PTR(7byte回滚指针),来指向上一个数据。也就是说每一行数据实际上会存在多个版本,同时每个版本都有自己的row_trx_id。

read view

read view实际上就是一个数组,在可重复读隔离级别下,事务启动的时候就会产生一个read view直到事务结束。

read view中存放的是当前活跃事务id,也就是当前还没有提交的事务id,如下图所示,假如在事务之间还存在一个活跃事务id为50,事务A的事务Id为51,事务B为52,事务C为53。那么事务A的read
view为[50,51],事务B为[50,51,52],事务C为[50,51,52,53]。

图片

高低水位

read view中最小事务id为低水位,而当前系统已经创建过的事务Id最大值加1,记作高水位。例如在事务A启动时由于read view为[50,51]那么高水位就是52,低水位为50,而事务B启动时由于read view为[50,51,52]那么高水位就是53,低水位为50。而之所以在可重读级别下能够始终看到的数据都和启动时候看的是一致的,原因就是因为高低水位加上一个当前事务id以及一个比对结果。

高低水位比对规则

1.如果row trx_id小于等于低水位落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;

2.如果row trx_id大于等于高水位落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
3.如果落在黄色部分,那就包括两种情况

3.1 若row trx_id在read view中,表示这个版本是由还没提交的事务生成的,不可见。



3.2 若row trx_id不在read view中,表示这个版本是已经提交了的事务生成的,可见。

图片

根据MVCC分析不同事务k的结果

前面说过在如下所示的执行结果中,事务B查询的k为3,事务A查询的结果为1,我们通过MVCC进行分析一下为什么是这样。 通过前面的建表可以知道此时的id为1的k实际数据为1,假设在事务A之前还存在一个事务同时事务id为50,事务A的事务id为51,事务B的事务id为52,事务C的事务id为53。

图片

前面说过在如下所示的执行结果中,事务B查询的k为3,事务A查询的结果为1,我们通过MVCC进行分析一下为什么是这样。

通过前面的建表可以知道此时的id为1的k实际数据为1,假设在事务A之前还存在一个事务同时事务id为50,事务A的事务id为51,事务B的事务id为52,事务C的事务id为53。

如下所示,此时在事务A中需要查询数据,然后在对应的版本控制链中进行查找,首先事务A的read view为[50,51],然后根据上面所说的比对规则,然后进行查询,查找到k为3时,此时row trx_id为52,此时也就是在黄色部分也就是说在将来发生的事务中,然后再次查找下一个,得到row trx_id为53,此时也比高水位大,所以也就是将来发生的事务,然后再找到row trx_id为50的发现是已经提交的事务,因为小于等于低水位。所以事务A查询的结果就是1。

图片

一致性读

所谓的一致性读就是指在可重复读隔离级别下,事务启动时看到的数据无论其他事务怎么修改,自己看到的数据都是和启动时候看到的数据时一致的。

更新逻辑

按照我们上面说的一致性读的话,此时如果按照上图所示,在事务B中查进行了一次更改操作,此时我们再次查询的时候应该是2而不是3,这是为什么呢?虽然事务C进行加1后变成了2,但是实际上事务B此时应该是看不到的,所以在事务B中应该是2,为什么就是3呢?

实际上在更新的时候采用了当前读的机制,也就是读最新的数据,如果不读最新的数据,那么就会导致数据丢失。所以MySQL是在更新的时候先拿到最新的数据也就是(1,2)然后在(1,2)的基础上进行加1操作,同时记录row
trx_id为52。

读已提交和可重复读区别

在MySQL中可重复读和读已提交都是通过MVCC进行实现的,却别在于可重读是事务启动的时候就生成read view整个事务结束都一直使用这个read view,而在读已提交中则是每执行一条语句就重新生成最新的read view。

网站文章

  • Hadoop实训任务3:HDFS和MapReduce综合操作

    Hadoop实训任务3:HDFS和MapReduce综合操作

    Hadoop实训任务3:HDFS和MapReduce综合操作

    2024-04-01 04:40:56
  • Mybatis快速入门(二)

    Mybatis快速入门(二)

    第四章 动态SQL什么是动态SQL:同一个dao的方法,根据不同的条件可以表示不同的sql与语句,主要是where部分有变化,使用mybatis提供的标签实现动态sql的能力,主要讲if,where,...

    2024-04-01 04:40:50
  • 自学一年Java能找到工作吗? 最新发布

    自学一年Java能找到工作吗? 最新发布

    从入门到找到工作大概6个月的时间吧。前提条件是每天都有大块的时间,并且可以处于高效的学习状态当中,。——如果这句话没有劝退大家继续看后面的内容。根据经验来说,我们先找到Java学习目的,一般都是以就业...

    2024-04-01 04:40:04
  • python自动化测试框架学习 之 HTMLtestrunner

    python自动化测试框架学习 之 HTMLtestrunner

    前几天大致搭了个基于python的自动化测试框架: 其中reports是用来放生成报告的====== HTMLtestrunner 是基于unittest单元测试的html的一个库。 首先安装,安装的时候有点特殊,一般的python库直接pip install ... 即可,安装这个的时候我用这种方式试着安装了下“testrunner”和“html-testrunner” 后面发...

    2024-04-01 04:39:58
  • layui分页样式设置居中

    layui分页样式设置居中

    layui数据表格

    2024-04-01 04:39:50
  • POST请求

    / 如果"POST"就会把这个请求的数据放在"请求数据包"-HTTPRequestMessage的请求体中。axios.post ("协议://ip:port/pathname",{参数对象})// ...

    2024-04-01 04:39:11
  • 计算机组成原理-数据的表示和计算

    1.进制的转换(1)进制的转换十进制转换为二进制(1)除2取余法(整数部分)对十进制的数不断除2求余,直到商为0。先求得的余数是二进制的低位(327)10转换:转换为 (101000111)2 第n次除2后的商 余数 163 1 81 1 40...

    2024-04-01 04:39:04
  • Hexo:博客框架10分钟搭建个人博客

    Hexo:博客框架10分钟搭建个人博客

    首先是先给大家打个招呼最近看网上看到了很多的的关于搭建博客的视频,我自己也学着自己搭建了一个博客"我自己的博客链接"(欢迎大家来我的博客跟我深入交♂流),今天我把搭建的过程记录下来写成博客,留下一个纪念,也可以顺便帮助那些想要搭建个人博客的小伙伴们,帮助他们搭建属于自己的博客主页啦,那么废话不多说,现在就开始吧。准备工作和环境搭建第一步安装node.js nodejs下载地址...

    2024-04-01 04:38:57
  • 二叉树相关公式

    二叉树的相关公式 1.一棵满二叉树,知道二叉树的层数c,求节点数m? 公式为:m=2^(c-1) ; 2.一棵满二叉树,知道二叉树的深度s,求节点数m? 公式为:m=2^s-1; 3.一棵满二叉树,知...

    2024-04-01 04:38:17
  • Linux常见的压缩包

    Linux常见的压缩包

    要创建一个归档文件,使用命令是:# lzma -c --stdoutexamplefile>examplefile.lzma虽然lzma是一款强大的工具,但其在Linux用户中的人气并不高。tar.g...

    2024-04-01 04:38:11