MySQL事务隔离-下

本文内容是整理极客时间的mysql课程。

  • 事务的隔离级别,如果是可重复读的隔离界别,事务T启动的时候会创建一个视图read-view,之后事务T执行期间,即使有其他事务修改了数据,事务T看到的数据和在启动的时候看到的一样。
  • 如果这个数据表中有行锁,一个事务A要更新一行,但是这行数据的行锁被另外一个事务B占有了,事务A就被锁住了,进入等待状态。那么等待事务A拿到行锁进行数据更新的时候,它读到的值是什么?事务B查到的K值是3,事务A查到的k值是1。
  • 为理解上述问题,举一个下面的例子,下面是一个只有两行的表的初始化语句。
    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);
  • 事务启动命令
    bfging/start transaction 命令并不是一个事务的起点,在执行到他们之后的第一个操作InnoDB表的语句,事务才真正启动。
    start transaction with snapshot 这个命令是马上启动一个事务,也就是马上建立一个快照。
    如果事务没有显式的使用begin/commit,表示这个update语句本身就是一个事务,语句完成的时候自动提交。
  • MySQL里的两个“视图”的概念
    • 1、view,他是一个用查询语句定义的虚拟表,调用的时候用查询语句生成的。创建视图的语法是create view... ,而它的查询方法与表一样。
    • 2、InnoDB在实现MVCC时用到的一致性视图,即 consistent read view,用于支持RC(read committed,读提交)和RR(repeatable read,可重复读)隔离级别的实现。它没有物理结构,用于事务执行期间定义“我能看到什么”
  • “快照”在MVCC里是怎么工作的?
    • 在可重复读的隔离级别下,事务在启动的时候就“拍了个快照”,注意,这个快照是基于整个库的。
    • “快照”的实现
      InnoDB里面每个事务有一个唯一的事务ID,叫做transaction id。它是事务在开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。
    • 一个记录被多个事务连续更新后的状态。
    • undo log(回滚日志),语句更新生成的undo log日志,图2中的三个虚线箭头,就是undo log。v1,v2,v3并不是物理上真实存在的,而是每次需要的时候根据当前版本和undo log计算出来的。
    • 一个事务启动后,其他事务操作的数据哪些是他能看见的,哪些是他看不见的?
      • 理解这个之前,先要知道“视图数组”
        InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正”在活跃“的所有事务ID,"活跃"指的是,启动了但是还没有提交。
        数组里存的是事务的ID,里面事务ID的最小值标记为低水位,里面事务ID的最大值+1标记为高水位。
        视图一致性的问题就靠这个“视图数组“解决了
      • 视图数组把所有row trx_id 分成了几种不同的情况,(row trx_id 是某行数据的版本号,由transaction id 赋值产生)
      • 对于当前事务的启动瞬间来说,一个数据版本的row trx_id,有以下几种可能:
        1、如果落在绿色部分,数据是可见的,表示这个版本是在当前事务启动之前提交的,或者是当前事务自己生成的。
        2、如果落在红色部分,数据不可见的,表示这个版本是将来启动的事务生成的。
        3、如果落在黄色部分,那就是包括两种情况。
        a、诺row trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见;
        b、诺row trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见;
      • 事务A查询数据逻辑图
      • 事务A查询语句的读书节流程是:
        1、找到(1,3)的时候,判断出row trx_id =101,比高水位大,处于红色区域,不可见;
        2、接着,找到上一个历史版本,一看row trx_id =102,比高水位大,处于红色区域不可见,
        3、再往前找,找到(1,1),他的row trx_id= 90,比低水位小,处于绿色区域,可见。
      • 对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:
        1、版本未提交,不可见;
        2、版本已提交,但是是在视图创建后提交的,不可见;
        3、版本已提交,而且是在视图创建前提交的,可见
  • 更新逻辑
    • 事务B更新逻辑图
    • 更新数据都是先读后写的,而这个读,只能读当前的值,称为”当前读“(current read)。
    • 除了update语句以外,select语句如果加锁,也是当前读。
      如果把事务 A 的查询语句 select * from t where id=1 修改一下,加上 lock in share mode 或 for update,也都可以读到版本号是 101 的数据,返回的 k 的值是 3。
      下面这两个 select 语句,就是分别加了读锁(S 锁,共享锁)和写锁(X 锁,排他锁)。
    • 假设事务C不是马上提交而是,变成如下图所示的事务C',会怎样?
      事务B启动后,事务C'启动,并更新了k的值,在事务C'还没有提交的时候(事务C'没提交,也就是(1,2)这个版本的写锁没有释放),而事务B要更新k的值,首先事务B要读取到最新版本(因为事务B是update的),而且要加锁,这是就被锁住了,必须等事务C’释放这个锁,才能继续它当前读。
    • 事务B更新逻辑图
    • 可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。
    • 读提交和可重复读的逻辑类似,主要区别是:
      1、在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后的事务里的其他查询都共用这个一致性视图;
      2、在读提交隔离级别下,每一个语句执行前都会重新计算出一个新的视图。
  • 在读提交隔离级别下,事务A和事务B的查询语句查到的k,分别应该是多少?
    注意:”start transaction with consistent snapshot;"的意思是从这个语句开始,创建一个持续整个事务的一致性快照。所以在读提交隔离级别下,这个用法就没意义了。等效于普通的start transaction。
    • 读提交隔离级别下的事务状态图
      这时,事务A的查询语句的视图数组是在执行这个语句的时候创建的,时序上(1,2)、(1,3)的生成时间都在创建这个视图数组的时刻之前。但是在这个时刻:
      1、(1,3)还没提交,属于情况1,不可见;
      2、(1,2)提交了,属于情况3,可见;
      所以事务A查询语句返回的是k=2;
      事务B查询的结果是k=3;
  • 小结
    • InnoDB 的行数据有多个版本,每个数据版本有自己的 row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性。
    • 对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
    • 对于读提交,查询只承认在语句启动前就已经提交完成的数据;
    • 因为表结构没有对应的行数据,也没有 row trx_id,因此只能遵循当前读的逻辑。

posted on 2019-09-09 20:50  拾掇的往昔  阅读(92)  评论(0编辑  收藏  举报

导航