目录

快照读

可重复读级别下的MVCC

两阶段锁

读已提交级别下的MVCC

总结


快照读

在可重复读隔离级别下,事务在启动的时候就“拍了个快照”。注意,这个快照是基于整库的,快照是如何实现的?

transaction id: InnoDB每个事务有唯一的事务ID,叫作transaction id。它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。

每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成⼀个新的数据版本,并且把transaction id赋值给这个数据版本的事务ID,记为row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。

        也就是说,数据表中的一行记录,其实可能有多个版本(row),每个版本有自己的row trx_id。

下图,表示⼀个记录被多个事务连续更新后的状态:

图中虚线框是同一行数据的4个版本,当前最新版本是V4,k的值是22,它是被transaction id 为25的事务更新的,因此它的 row trx_id也是25。

我们知道,语句更新是会生成undo log(回滚日志),图2中的三个虚线箭头,就是undo log;

        V1、V2、V3并不是物理上真实存在的,而是每次需要的时候根据当前版本和undo log计算出来的。例如需要V2的时候,就是通过V4依次执行U3、U2算出来。


可重复读级别下的MVCC

可重复读的定义:⼀个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。(前后读取的数据需要一致)。

因此,事务只需要在启动的时候声明说,“以我启动的时刻为准,如果⼀个数据版本是在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本”。

当然,如果“上⼀个版本”也不可见,那就得继续往前找。还有,如果是这个事务自己更新的数据,它自己还是要认的。

MVCC的实现:

InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务ID。“活跃”指的就是,启动了但还没提交的事务。

        数组里事务ID的最小值记为低水位,当前系统里面已经创建过的事务ID的最大值加1记为高水位。

视图数组和高水位,就组成了当前事务的一致性视图(read-view)。数据版本的可见性规则,就是基于数据的row trx_id和这个⼀致性视图的对比结果得到的。

视图数组把所有的row trx_id 分成了几种不同的情况:

对于当前事务的启动瞬间来说,⼀个数据版本的row trx_id,有以下几种可能:

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

2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;

3. 如果落在黄色部分,那就包括两种情况

a. 若 row trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见;

b. 若 row trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见。


建立如下表,并插入数据:

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

三个事务ABC的执行流程,括号内表示事务ID:

标注一下,begin/start transaction 命令并不是⼀个事务的起点,在执行到它们之后的第⼀个操作InnoDB表的语句(第一个快照读语句),事务才算是真正启动。如果想要马上启动一个事务,使用start transaction with consistent snapshot 这个命令。

事务C没有显式地使用begin/commit,表示这个update语句本身就是⼀个事务,语句完成的时候会自动提交。 事务B在更新了一行之后查询; 事务A在⼀个只读事务中查询,并且时间顺序上是在事务B的查询之后。

先假设事务A开始前,系统里面只有⼀个活跃事务ID是99;

事务A、B、C的版本号分别是100、101、102,且当前系统只有这四个事务;

三个事务开始前,(1,1)这一行数据的row trx_id是90。

因此,事务A的视图数组就是[99,100]; 事务B的视图数组是[99,100,101];  事务C的视图数组是[99,100,101,102]。

经过上图可以看到生成的版本链如下所示:

事务A要来读数据了,它的视图数组是[99,100],事务A查询语句的读数据流程是这样的:

1. 找到(1,3)的时候,判断出row trx_id=101,比当前视图数组内高水位ID大,处于红色区域,不可见;

2. 接着,找到上⼀个历史版本,⼀看row trx_id=102,比高水位大,处于红色区域,不可见;

3. 再往前找,终于找到了(1,1),它的row trx_id=90,比低水位小,处于绿色区域,可见。

这样执行下来,虽然期间这一行数据被修改过,但是事务A不论在什么时候查询,看到这行数据的结果都是⼀致的(可重复读隔离级别下),所以也称之为一致性读。

简化一下过程以上的判断过程:

⼀个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

1. 版本未提交,不可见;

2. 版本已提交,但是是在视图创建后提交的,不可见;

3. 版本已提交,且是在视图创建前提交的,可见。

再用这个规则来判断事务A的查询结果,事务A的查询语句的视图数组是在事务A启动的时候生成的,这时候:

(1,3)还没提交,属于情况1,不可见;

(1,2)虽然提交了,但是是在视图数组创建之后提交的,属于情况2,不可见;

(1,1)是在视图数组创建之前提交的,可见。事务A的查询结果为 K  = 1 。

去掉数字对比后,只用时间先后顺序来判断,分析起来是不是轻松多了。


再来分析一下事务B的查询语句结果,事务B查询到的结果值 K = 3 :

根据上图事务执行顺序,再来看看事务B此时的版本链情况:

事务B的视图数组是先生成的,之后事务C才提交(事务C更新完后就自动提交了),不是应该看不(1,2)吗,怎么能算出(1,3)来?

如果事务B在更新之前查询一次数据,这个查询返回的k的值确实是 K = 1,

但是,当它要去更新数据的时候,就不能再在历史版本上更新了,否则事务C的更新就丢失了。因此,事务B此时的set k=k+1 是在(1,2)的基础上进行的操作。

所以,这里用到了这样⼀条规则:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。

        因此,事务B在更新的时候,当前读拿到的数据是(1,2),更新后生成了新版本的数据(1,3),这个新版本的row trx_id是101,也就是事务B的 trx_id。

接着,在执行事务B查询语句的时候,⼀看自己的版本号是101,最新数据的版本号也是101,是自己的更新,可以直接使用, 所以查询得到的k的值是3。

        补充一下,除了update语句外,select语句如果加锁,也是当前读。


两阶段锁

再往前⼀步,假设事务C不是马上提交的,变成了下面的事务C,会怎么样:

与前面不同的是,事务C更新后并没有马上提交,在它提交前,事务B的更新语句先发起了。虽然事务C还没提交,但是(1,2)这个版本也已经生成了,并且是当前的最新版本。

那么,事务B的更新语句会怎么处理?

        两阶段锁协议:在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释 放。

事务C没提交,也就是说(1,2)这个版本上的写锁还没释放。 事务B是当前读,必须要读最新版本,且必须加锁,因此就被锁住了,必须等到事务C释放这个锁,才能继续它的当前读:

因此,到这里可以总结,事务的可重复读的能力是怎么实现的?

  可重复读的核心就是⼀致性读(consistent read);

        事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。


读已提交级别下的MVCC

读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

  • 可重复读隔离级别下:只需要在事务开始的时候创建⼀致性视图,之后事务的其他查询都共用这个⼀致性视图。
  • 读提交隔离级别下:每⼀个语句执行前都会重新算出⼀个新的视图。

再看⼀下,在读提交隔离级别下,事务A和事务B的查询语句查到的k,分别应该是多少?

说明一下,“start transaction with consistent snapshot; ”的意思是从这个语句开始,创建⼀个持续整个事务的⼀致性快照。所以,在读提交隔离级别下,这个用法就没意义了,等效于普通的start transaction。

下面是读提交时的状态图,可以看到这两个查询语句的创建视图数组的时机发生了变化,就是图中的read view框:

事务A的查询语句的视图数组是在执行这个语句的时候创建的,时序上(1,2)、(1,3)版本链的生成时间都在创建这个视图数组的时刻之前,我们再次根据以下规则:

⼀个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

1. 版本未提交,不可见;

2. 版本已提交,但是是在视图创建后提交的,不可见;

3. 版本已提交,且是在视图创建前提交的,可见。

(1,3)还没提交,属于情况1,不可见;

(1,2)提交了,属于情况3,可见,因此事务A读到的K = 2 。

显然,事务B查询到的 K = 3 。


总结

InnoDB的行数据有多个版本,每个数据版本有自己的row trx_id,每个事务或者语句有自己的⼀致性视图。

        普通查询语句是⼀致性读,⼀致性读会根据row trx_id和⼀致性视图确定数据版本的可见性。

        

  • 可重复读级别,查询只承认在事务启动前就已经提交完成的数据(事务开始的时候创建⼀致性视图)
  • 读已提交级别,查询只承认在语句启动前就已经提交完成的数据(每⼀个语句执行前都会重新算出⼀个新的视图。)

当前读,总是读取已经提交完成的最新版本。

MySQL - MVCC相关推荐

  1. mysql 锁机制 mvcc_轻松理解MYSQL MVCC 实现机制

    轻松理解MYSQL MVCC 实现机制 轻松理解MYSQL MVCC 实现机制 #### 1. MVCC简介 ##### 1.1 什么是MVCC MVCC是一种多版本并发控制机制. ##### 1.2 ...

  2. mysql mvcc

    mysql MVCC MVVC 实现:排他锁+undolog+版本事务链+一致性read-view视图+版本事务链匹配规则 一致性非锁定读 在 InnoDB 存储引擎中,多版本控制 (multi ve ...

  3. mysql MVCC不能避免幻读

    幻读 并发情况下,A事务读取了一条记录,此时B事务插入一条记录,A事务又读取,读到了两条数据,此时就造成了读取数据不一致,一般到这幻读通常说的是事务提交了,而且是指删除.插入带来的问题. 问题 mys ...

  4. mysql mvcc实现乐观锁_MVCC-乐观锁

    Mysql MVCC是乐观锁的一种实现方式,但并不是MVCC就等于乐观锁.乐观锁( Optimistic Locking)其实是一种思想.相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以 ...

  5. mysql mvcc update_MySql MVCC机制

    Multiversion concurrency control 多版本并发控制 并发访问(读或者写)数据库时,对正在事务内处理的数据做多版本的管理,用来避免由于写操作的堵塞,而引发读操作失败的并发问 ...

  6. 从ReadView深入理解MySql MVCC原理

    MySql MVCC 机制 在说MVCC机制前我们先了解ReadView. ReadView是什么呢?在我们平时执行一个事务的时候,就会生成一个ReadView,ReadView的组成结构大致如下 参 ...

  7. 理解MYSQL MVCC 实现机制

    1. MVCC简介 1.1 什么是MVCC MVCC是一种多版本并发控制机制. 1.2 MVCC是为了解决什么问题? 大多数的MYSQL事务型存储引擎,如,InnoDB,Falcon以及PBXT都不使 ...

  8. MYSQL MVCC实现及其机制

    多版本并发控制 Multiversion Concurrency Control 大部分的MySQL的存储 引擎,比如InnoDB,Falcon,以及PBXT并不是简简单单的使用行锁机制.它们都使用了 ...

  9. mysql mvcc 隔离级别_隔离级别和MVCC

    -----本文章为个人理解,如有疑问或错误欢迎留言并讨论----- 谢谢. 昨天去去哪儿网面试,老周和老赵问了很多问题,大多关于细节,其中就包括事务隔离级别和MVCC,由于准备不够充分,所以今天特地进 ...

  10. mysql mvcc 隔离级别_关于 Mysql 四种隔离级别中 Lock 和 MVCC 的关系

    读写锁 共享锁(share lock)| 读锁(read lock) 读锁是共享的,或者说是相互不阻塞的.多个客户在同一时刻可以同时读取同一个资源,而互不干扰 SELECT ... LOCK IN S ...

最新文章

  1. iOS架构设计-URL缓存(上)
  2. linux curl没有内容,curl在tcpdump中没有显示输出
  3. mysql递归查询 缓存_MySQL-递归查询方法解析
  4. Android 滑动定位+吸附悬停效果实现
  5. ubuntu16使用labelImg
  6. Arduino Uno 实验3——蜂鸣器
  7. EXCEL对比两列中查找相同的数据
  8. 大量大数据如何进行查询
  9. (Qt)windows下鼠标键盘热插拔监测
  10. life's a struggle - 宋岳庭
  11. 优雅的使用vue+Dcloud(Hbuild)开发混合app
  12. double team
  13. 电脑如何录制在线课程?-QVE屏幕录制
  14. 扎实的PHP编程基础,PHP的一些基础编程题
  15. java listview用法_ListView的使用(一)
  16. axios进行二次封装
  17. 数据库系统概念 | 第六章:形式化关系查询语言 | 含带答案习题
  18. WRF模式运行出错记录(基本已解决)
  19. 海康威视iVMS综合安防系统任意文件上传漏洞复现(0day)
  20. Android游戏开发之数独课时----2

热门文章

  1. javascript学习指南,这个车架号是什么车?通过车架号查车辆信息!
  2. CentOS下安装TTF字体
  3. mysql批量更新方法
  4. 无线路由器硬件配置参数 NetGear篇
  5. 梦幻手游服务器维护摆摊公示时间,梦幻西游手游3月30日维护公告
  6. ajax 跨域请求数据,JQuery Ajax执行跨域请求数据的解决方案
  7. MySQL8中的11个窗口函数
  8. RxJava2+Retrofit2+RxLifecycle3+OkHttp3网络请求封装(动态演示)
  9. 移动端页面数字占用空间不同的问题
  10. Google Colab基础使用指南