前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mysql事务隔离级别

Mysql事务隔离级别

作者头像
小土豆Yuki
发布2020-08-28 11:18:54
1.4K0
发布2020-08-28 11:18:54
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

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

  • 读未提交读

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

  • 读已提交度

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

  • 可重复读

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

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

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

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档