事务隔离机制--可见性详解


事务不同的启动方式创建视图的时机不同:

  1. begin/start transaction:一致性视图是在第执行第一个快照读语句时创建的;
  2. start transaction with consistent snapshot:在执行这行语句时一致性视图创建的。

MySQL中视图的概念:

  1. 一个是view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是create view … ,而它的查询方法与表一样。
  2. InnoDB在实现MVCC时用到的一致性读视图(consistent read view),用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。

1 MVCC中"快照"的工作机制

1.1 "快照"的实现方式

在可重复读隔离级别下,事务在启动的时候就“拍了个快照”。

transaction id 和 row trx_id:

  1. transaction id:InnoDB里面每个事务唯一的事务ID,在事务开始的时候向InnoDB的事务系统申请的,严格递增的;
  2. row trx_id:每次事务更新数据的时候,都会生成一个新的数据版本,并且把transaction id赋值给这个数据版本的事务ID,这个ID记为row trx_id。

数据表中的一行记录,其实可能有多个版本(row),每个版本有自己的row trx_id,如下图所示,一个记录被多个事务连续更新的情况:
在这里插入图片描述

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

undo log:图中的三个虚线箭头就是undo log,V1、V2、V3不是物理真实存在的,每当需要时根据当前版本undo log计算得出。

1.2 事务的可见性实现

可重复读:一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。

如何实现的:

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

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

    在这里插入图片描述

    对于当前事务的启动瞬间来说,一个数据版本的row trx_id有下面几种情况:

    1. 在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,数据可见
    2. 在红色部分,表示这个版本是由将来启动的事务生成的,数据不可见
    3. 在黄色部分,
      1. 若row trx_id在数组中,表示这个版本是由还没提交的事务生成的,数据不可见
      2. 若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);

比如如下图3个事务操作:
在这里插入图片描述

做3条假设:

  1. 事务A开始前,系统里面只有一个活跃事务ID是99;
  2. 事务A、B、C的版本号分别是100、101、102,且当前系统里只有这四个事务;
  3. 三个事务开始前,(1,1)这一行数据的row trx_id是90。

所以,事务A的视图数组就是**[99,100], 事务B的视图数组是[99,100,101], 事务C的视图数组是[99,100,101,102]**,如下图所示
在这里插入图片描述

事务A查询语句的读数据流程:

  1. 找到(1,3)的时候,判断出row trx_id=101,比高水位大,处于红色区域,不可见;
  2. 接着,找到上一个历史版本,row trx_id=102,比高水位大,处于红色区域,不可见;
  3. 再往前找,row trx_id=90,比低水位小,处于绿色区域,可见。

事务A无论在什么时候查询,即使这行数据被修改过,看到这行数据的结果都是一致的,这种被称为一致性读

总结下规律:

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

  1. 版本未提交,不可见;
  2. 版本已提交,但是是在视图创建后提交的,不可见;
  3. 版本已提交,而且是在视图创建前提交的,可见。

用这个规则再看这个例子:

在可重复读情况下,事务A创建完视图做查询时:

  • (1,3)还未提交,不可见;
  • (1,2)已经提交了,但是在视图数组创建之后,不可见;
  • (1,1)是在视图数组创建前提交,可见。

所以事务A中查询结果还是1。

2 更新操作

2.1 当前读

事务B和事务C做更新操作,若事务B中在更新完后再做查询,拿到的值会是多少?如下图所示:
在这里插入图片描述

更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。

事务B的操作:

  1. 更新时进行当前读,拿到的数据是(1,2),更新后变成(1,3),新版本的row trx_id是101;
  2. 查询时,发现自己版本号为101,最新数据版本也是101,这时就可以直接使用,查询到的k值就是3。

当前读的场景:

  1. update语句;

  2. select语句加锁:

    1. select k from t where id=1 lock in share mode;
      

      读锁;

    2. select k from t where id=1 for update;
      

      写锁。

2.2存在行锁场景的当前读

有A,B,C三个事务,与上面例子唯一区别是,事务C’更新后不立刻提交,在它提交件事务B的更新语句先执行。如下图所示
在这里插入图片描述

这时事务B会怎样处理?

事务B是当前读,必须读最新数据,因为事务C’事务还未提交,根据两阶段锁协议,该行数据会被锁住,事务B的更新操作就必须等到事务C’释放行锁才能继续当前读

2.3读已提交与可重复读的可见性区别

相同点:

  1. 两者在一致性读上都符合:

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

    1. 版本未提交,不可见;
    2. 版本已提交,但是是在视图创建后提交的,不可见;
    3. 版本已提交,而且是在视图创建前提交的,可见。
  2. 两者在更新数据时:

    都只能当前读,并且如果当前记录的行锁被其他事务占用,就必须进入锁等待。

区别:创建视图的时机不一样

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

举例:

在读已提交隔离级别下,事务A,B读到的值是多少,如下图所示:
在这里插入图片描述

事务A的查询语句的视图数组是在执行这个语句的时候创建的,

  1. 事务B的更新还未提交,不可见;
  2. 事务C的更新已提交,可见。

所以,A查询返回k=2,事务B在更新后查询属于当前读,所以k=3。

  • 37
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值