专栏首页洁癖是一只狗Mysql事务隔离级别

Mysql事务隔离级别

事物的个隔离级别是非常重要的概念,Mysql的隔离级别有以下几种

  • 读未提交读

在所有事物中可以看到事物没有提交的结果,实际应用中是很少的,他的性能也不比其他隔离级别好很多,读到未提交的结果导致脏读

  • 读已提交度

大多数据库的默认隔离级别,但是不是mysql的默认级别,一个事物只能看到已经提交的结果,他也支持不可重复读,在同一个事物的其他实例在该实例中修改的数据,导致两次select的结果可能不一样

  • 可重复读

mysql的默认隔离级别,在事务开始的时候,直到事务结束看到的行的数据都是一样,这种隔离级别是会产生幻读,幻读就是在用户读取某一范围的数据时候,其他事物新增了一条数据,用户再次读取的时候,返现多了一行数据(幻读是指读到了其他事务的inset,不可重复读是指读到了其他事物的delete/update)

  • 串行化 这种隔离级别就是使事物严格按照顺序执行,就是在每一行数据上加上锁,保证了事物不可冲突,避免了幻读,脏读,不可重复读,但是增加了锁超时和锁竞争

之前我们说过在可重复读级别下,事物T开始的时候会创建一个read-view,之后再事物T期间的其他事物修改了数据,对于事务T也是不可见的,但是在我上一篇说过,一个事物在修改一行数据的时候,发现这行数据已经被行锁锁住了,这个时候只能等待行锁被释放,但是在释放之后,他读取的值有事什么呢

首先,我们看一下例子如下建表语句

mysql> 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);

看看上面的例子,我们看看事物A 和事物B查询到的结果是什么,此时你老想一下,(没有特别说明,默认都是autocommit=1)

事物A的结果是1,而事物B的结果是3,不知道是否和你的答案一致呢,

为什么会是这样呢,首先我们得看看数据库的一个概念视图,数据库中有定义了两种视图

  • 一个视图view是在一个查询语句生成的一个虚拟表,在调用查询语句并生成结果,创建的语句可用 create view ,查询方式和表的方式一样
  • 另一个视图则是innodb中的一致性视图,consistent read view ,用于隔离级别读已提交和可重复读的实现

快照在MVCC中是如何使用的呢

在开启一个事物的时候,就会拍个照,这个快照是对于整个库的,但是我们想象,对于一个100G的数据库,我们不可能把所有的数据全部复制一次,但是为什么开始一个事物还是很快呢,

实际上每一个事物的开启,都会创建一个事物id ,transaction id,他是向数据库申请,严格按照顺序递增的,而每一行数据都是有多个版本的,每一个事物更新数据后都会把此时的事物id,赋值给每一个版本的事物id,即row tx_id,并且保留旧的数据,并且在新的数据版本都可以直接拿到旧版本的数据,正如下图,一行数据多次更新的版本状态

如上图显示,目前最新的版本是k=22,他是的事物id=15,因此他的row trx_id=15,之前我们也提到过,语句的更新会产生undo log,其实上面的三个实线箭头就是undo log,而实际上v1,v2,v3不是真实存在的,而是有当前版本和undo log计算出来的。

在可重复读隔离级别,我们知道在事物启动的时候,只能看到事物启动前提交的数据,之后生成的版本我们是不认的,当然自己修改的数据还是要认的,

在实际应用中,每一个事物都会有一个数组,数组保存的是当前系统活跃的事物id,活跃的是指,启动但未提交的事物id。

  • 低水位,是指数组中最小的事物id
  • 高水位,是指数组中最大的事物id+1

一致性视图是有视图数组和高水位组成如下图

数据的可见性是根据数据的row trx_id和一致性视图判断的

这样,当一个事物启动的瞬间,row trx_id可能有以下几种情况

  • 如果落在绿色部分,表示事物已经提交,对当前事物可见
  • 如果落在红色部分,表示事物未启动,对当前事物不可见
  • 如果在黄色部分有两种情况
    • 如果在视图数组中,表示事务没有提交,可见
    • 如果不在视图数组中,表示事务已经提交,不可见

到这里,我们回过来看看开头我们的问题,为什么事务A的k=1,而事物B的k=3

此时假设一下如下

  1. 事物A开启前,只有一个活跃事务row trx_id=99
  2. 事物A,B,C的版本分别是100,101,102
  3. 三个事物开启前此时(1,1)row trx_id=90

因此根据上面的可见性规则判断如下

上图中看到事物c先把(1,1)更新成了(1,2)此时row trx_id=102,然后事物B更新(1,2)为(1,3),此时的row trx_id=101,

此时我们来看看事物A查询的数据如何获取

  1. 此时事务A的视图数组是[99,100]
  2. 找到当前版本(1,3)此时的row trx_id=101,落在了红色部分,不可见
  3. 向上寻找上一个版本(1,2)此时的row trx_id=102,落在红色部分,不可见
  4. 向上寻找上一个版本(1,1),此时row trx_id=90,落在了绿色部分,可见
  5. 因此此时的k=1

上面的判断是从代码逻辑进行判断,其实我们可以按照下面规则进行判断

  1. 版本未提交,不可见
  2. 版本提交,是视图创建后,不可见
  3. 版本提交,是视图创建前,可见

我们也验证一下上面的规则如下

  1. (1,3)版本未提交,不可见
  2. (1,2)版本提交,但是视图创建后提交,不可见
  3. (1,1)版本提交,且是视图创建前提交,可见

但是有人会发现事物B 的update语句感觉是不有问题呢,为什么会是在(1,2)基础上进行增加的,事物C的视图不是后面才创建的吗

如果不是按照历史版本更新的话,事物c的更新不是就丢失了吗,导致读到的数据是脏读,那究竟是为什么的,这里我们要加一条规则,uodate的时候,是先读在写的,而这个读必须读取当前值,这种叫做当前读,

除了update语句外,我们的select 如果加锁,也是使用当前读,如果使用下面语句读取到的k=3

select k from t where id =1 lock in share mode
select k from t where id =1 for update

我们再进一步分析事务C如果不是自动提交,而是在下面的事务C1

此时(1,2)已经生产,但是事物没有进行提交,那么事物B的更新语句如何执行,这就要提到了上一节说的行锁,此时id=1被行锁锁住,事物B的当前读,必须等待id=1的行锁释放后,才能使用当前读。

可重复读的核心就是一致性视图,更新的时候只能用当前读,如果当前记录被行锁锁定,必须等待释放,再去更新,

读已提交和不可重复读的逻辑类似,主要有以下区别

  • 在可重复读下,事物创建的建立的一致性视图,之后的其他语句通用这一个一致性视图
  • 读已提交,是事物中每一个语句都会新建一个一致性视图

我们再来看看读已提交的情况,如下图

事物A在获取查询语句的时候创建视图,(1,3)(1,2)此时生成的时刻是在创建视图之前,因此

  • (1,3)此时还没有提交,不可见
  • (1,2)此时已经提交,可见
  • 因此事物A,k=2
  • 显而易见事务B,k=3

本文分享自微信公众号 - 洁癖是一只狗(rookie-dog),作者:洁癖汪

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java并发包--ThreadlocalRandom原理解密

    ThreadLocalRandom是JDK1.7新增的随机生成器,我们今天来说明为什么要JUC下要新增这个类,以及解密它的原理

    小土豆Yuki
  • 并发编程问题为什么都很诡异

    并发编程对于很多人说都是比较难的,总是出现一些莫名其妙的bug,让我们很是苦恼,那么他到底是难在哪里呢,今天就带大家看看引起并发bug的根源

    小土豆Yuki
  • Mysql数据--死锁解密

    Mysql行锁是在引擎中实现的,并不是所有的存储引擎都支持行锁,比如myisam就不支持行锁,而innodb支持行锁,myisam在并发度高的系统中就会影响系统...

    小土豆Yuki
  • JAVA动态创建表以及动态插入数据

    利用JDBC驱动链接Mysql数据其实很简单的,第一要下载一个名为 “mysql-connector-java-5.1.20-bin.jar” 驱动包。并解压到...

    ZONGLYN
  • 译:如何用Swift进行TDD(测试驱动开发)

    rectinajh
  • 2.MySQL表操作

    表就相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段

    changxin7
  • 2-1 是否同一棵二叉搜索树 (35 分)

    给定一个插入序列就可以唯一确定一棵二叉搜索树。然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到。例如分别按照序列{2, 1, 3}和{2, 3, 1}插...

    韩旭051
  • UNIX网络编程卷1(第三版)基本TCP套接字编程

    其中family参数指明协议族,type参数指明套接字类型,proctocol参数为协议类型或者0

    心跳包
  • 使用requests来爬取大众点评

    在工作生活中,发现越来越多的人对大众点评的数据感兴趣,而大众点评的反爬又是比较严格的。采取的策略差不多是宁可错杀一万,也不放过一个。有的时候正常浏览都会跳出验证...

    周小董
  • 苹果WWDC:不搞AI优先,先用AI来加速你的手机应用

    今天的WWDC,除了年度硬件、系统更新,大家更为关注的是苹果是否也去选择“AI优先”的战略,因为它的老对手微软和Google早已经走上这条路了。 两个半小时的K...

    AI科技大本营

扫码关注云+社区

领取腾讯云代金券