第八章 事务隔离还是不隔离

第八章 事务隔离还是不隔离

Innodb支持 RC(读提交) 和 RR(可重复读) 隔离级别实现是用的一致性视图(consistent read view)

事务的启动时机

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

举例

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

Untitled

为什么事务 B 查到的 K 的值是 3,而事务 A 查到的值是 1 ?

  • 事务B之所以查到的是3,是因为事务B的更新是在事务C更新的基础之上修改的。
  • 在修改的时候,需要先读然后再修改,事务B读取的时候,确实不会读取到事务C的更新后的内容。
  • 但是事务 B 进行更新的时候,会执行当前读,所谓当前读就是读取当前数据库中最新的数据内容,如果不执行当前读,而直接基于一致性读的前提下,直接执行更新操作,将会把事务 C 的更新给丢失掉。
  • 所以事务B更新的时候,是在已经提交的事务C的基础上更新数据。所以事务B就出现一个现象:执行更新前查询的值为(1,1),执行完更新之后,会变成(1,3),而不是(1,2)。
  • MySQL中读取数据的时候总是一致性读:事务开始前会创建一个一致性视图,在事务执行过程中,所有其他未提交或者已经提交的事务,对当前已经开启的事务不可见。
  • 但是在更新数据的时候,会执行当前读:意思是在更新之前会去数据库中获取最新的已经提交的事务数据内容,然后基于已经提交的事务的基础上更新自己的内容。

MySQL 的视图

  • 查询语句虚拟表 (view)
  • InnoDB 实现MVCC时的一致性读视图 (consisitent read view)

数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id (事务ID)

如图:一个记录被多个事务连续更新后的状态

Untitled

图中虚线箭头就是 undo log

数据版本的可见性

  • InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交的事务。
  • 数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。
  • 这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。

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

Untitled

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

  1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
  2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
  3. 如果落在黄色部分,那就包括两种情况:
    1. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
    2. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。

举例

例如,开启一个事物的启动瞬间的事物 id :1,2,3,4,5,6,7,8,9; 其中活跃的事物 id 组成视图数组:[3 , 4 , 5]

低水位:3

高水位为:9 + 1 = 10

针对一个数据版本的 row trx_id的几种情况:

  1. row trx_id < 3 时,表明这个版本是已提交的事物生成的,可见;
  2. row trx_id > 10时,表明这个版本是将来启动的事物生成的,不可见;
  3. row trx_id 在[3 , 4 , 5]中是不可见的。如果视图数组是 [3, 5],那么row trx_id在[3, 5]中是不可见的,但4已经提交,是可见的
  4. 5 < row trx_id < 10 时,表示是已经提交过的事物,所以可见;

不管是什么隔离级别,更新操作必然是 当前读

  • 数据最终的版本只有一个,每次每个事物在进行快照读的时候,会先取出最终版本,然后根据事物ID判断当前事物是否能读取当前版本数据,如果不行,则通过 undo log 获取前一个版本,继续同样的判断,直到拿到数据为止
  • 但是如果是更新操作,则必须要当前读,必须要拿到最新版本的数据
  • 此时,如果有其他事物对当前数据加锁了,那就必须等待锁释放
  • 即使是读未提交隔离级别下,更新数据也会加锁,更新的时候同样要等待锁释放

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

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

更新逻辑

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

  • 当前事务加锁读,此时若遇到其他事务持有该数据的写锁,便只能等待,最终得到其他事务修改后提交的最新值,也就是当前读。
  • 当然如果其他事务并没有对该数据进行修改写,加锁读和不加锁读其实没区别。
  • 总结一下,加锁读会导致读到最新修改的数值

假设事务 C 不是马上提交的,而是变成了下面的事务 C

Untitled

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

Untitled

事务的可重复读的能力是怎么实现的 ?

  • 可重复读的核心就是 一致性读(consistent read);
  • 而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

表结构为什么不支持“可重复读” ?

  • 表结构没有对应的行数据,也没有 row trx_id,因此只能遵循当前读的逻辑
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿小羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值