极客时间MySQL实战45讲笔记 七. 事务隔离

1. 启动事务语句

begin/start transaction语句执行时,并不会立即开启一个事务,在执行他后面第一个操作时,事务才真正启动。

start transaction with consistent snapshot语句立即开启事务。

视图概念

  • 一种是view,是通过语句创建的虚拟表。
  • 另一种是InnoDB在实现MVCC时用到的一致性读视图,即consistent read view,用于支持RC(Read Commit)和RR(Repeatable Read)的实现。

2. 可重复读级别下事务隔离实现

在可重复读隔离级别下,事务T启动时候会创建一个视图read-view,之后事务T执行期间,其他操作对其不受影响。

但是当事务T试图更新某一行数据时,但是这行数据的行锁又被其他事务持有,那么事务T会被阻塞,当持有行锁的事务执行结束,事务T读到的值是如何呢?

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

在以上的表上有如下操作:

事务A事务B事务C
start transaction with consistent snapshot;
start transaction with consistent snapshot;
update t set k = k + 1 where id = 1
update t set k = k + 1 where id = 1;<br />select k from t where id = 1;
select k from t where id = 1;<br />commit;
commit;

在以上操作中,事务A看到值为1,事务B看到值为3

2.2 事务快速启动原理

在可重复读隔离级别下,事务在启动的时候就**“拍了个快照”,这个快照是基于整个数据库的**。但这个过程并不需拷贝整个库的数据。是因为

InnoDB里面每个事务都有一个唯一的事务ID,即transaction id。是有InnoDB的事务系统严格按照递增顺序生成。

每个事务在对一行数据进行更新时,都会生成一个新的数据版本,即InnoDB中,一行数据有多个数据版本。每次事务更新数据时,会将事务的transaction id赋值对应的数据版本,记为row trx_id

以下示意图为某行数据被多个事务更新之后的状态

InnoDB为每个事务构造一个数组,用来保存这个事务启动瞬间,当前启动了但还没提交的所有事务ID。数组里面事务ID的最小值为低水位,当前系统里面已经创建过的事务ID的最大值加1几位高水位。这个视图数组和高水位,就组成了当前事务的一致性视图。

数据版本的可见性规则就是基于数据的row trx_id和这个一致性视图的对比结果得到。对于当前事务的启动瞬间来说,一个数据版本的row trx_id有以下几种可能:

  • 小于低水位的id,则标识这个版本的事务已经提交或者是当前事务自己生成,数据可见
  • 大于高水位,标识这个版本的事务是由将来的某个事务启动,数据不可见
  • 如果介于之间,则标识这个版本是由还没有提交的事务创建的,不可见

比如,对于上图中的数据,如果有一个事务,它的低水位是18,那么当他访问这一行数据时,就会从V4通过U3计算出V3,所以他读到的数据为11。

有了这个逻辑之后,那么系统随后发生的数据更新,都和这个事务无关了,所以这个事务的快照就是“静态”的。InnoDB正是利用了这个特征,实现了“妙计创建快照”的能力。

2.3 事务A查询逻辑

假设2.1的场景中,

1. 系统里面只存在一个活跃事务ID是99
2. 事务A,B,C的版本号分别为100,101,102,且当前系统里面只有这四个事务
3. 三个事务开始前,id为1的记录的数据row trx_id是90

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

在以上表格的操作中,事务C先把数据从1->2,此时最新的数据版本的row trx_id为102,然后事务B把数从2->3,这个时候的数据版本的row trx_id为101.

事务A在查询的时候,当前数据版本101 > 100,数据对事务A不可见,接着找上一个版本,即102,依然不可见,继续找,找到90<99,可见,取出此时的值即 1.

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

  • 版本未提交,不可见
  • 版本已提交,但是是在视图创建之后提交的,不可见
  • 版本已提交,而且是在视图创建之前提交的,可见
2.4 事务B更新逻辑

按照2.2 中的规则,事务B在更时,看到数据应该1才对,因为事务B在更新时,当前的数据版本row trx_id为102 > 101,应该是不可见的。

但是,InnoDB中更新数据时,遵循另外一个规则:

  • 更新数据都是先读读后写的,而且这个读只能读当前的值,成为“当前读”。

因此更新时,拿到的数据是2,而不是1

除了update是用了当前读之外,如果select加了锁也是当前读。例如:

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

如果事务C的操作步骤改成以下情况

事务A事务B事务C`
start transaction with consistent snapshot;
start transaction with consistent snapshot;
start transaction with consistent snapshot;<br />update t set k = k + 1 where id = 1
update t set k = k + 1 where id = 1;<br />select k from t where id = 1;
select k from t where id = 1;<br />commit;commit;
commit;

此时,由于两阶段更新的协议,此时事务B的更新操作会被阻塞,直到事务C`提交。

3. 事务的可重复的能力实现总结

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

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

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

InnoDB的行数据有多个版本,每个数据版本都有自己的row trx_id,每个事务或者与都有自己的一致性视图。普通查询语句是一致性读,一致性读会根据row trx_id和一致性视图确定数据版本的可见性。

  • 可重复读,查询只承认在事务启动前就已经提交完成的数据
  • 读提交,查询值承认在语句启动前就已经提交完成的数据
  • 当前读,总是读取已经提交完成的最新版本

转载于:https://my.oschina.net/u/575836/blog/3026983

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值