观察事务隔离性

事务什么时候开启

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。
如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。

	第一种启动方式,一致性视图是在第执行第一个快照读语句时创建的;
	第二种启动方式,一致性视图是在执行 start transaction with consistent snapshot 时创建的。

Mysql 中的两个视图

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

MVCC中的快照

在可重复读隔离级别下,事务在启动的时候就“拍了个快照”。注意,这个快照是基于整库的。
undo 回滚日志 每次修改 语句更新会生成 undo log(回滚日志)
原来的值 并不是物理上真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 版本2 的时候,就是通过 版本44 依次执行 Undo log3、Undo log2 算出来
数据 当前的值是多少 版本是多少 是被那个事务所更新的

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。
数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id

可重复读和MVCC

为什么在可重复读的过程中,看到的数据都是一致得

一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。
当然,如果“上一个版本”也不可见,那就得继续往前找。还有,如果是这个事务自己更新的数据,它自己还是要认的。

InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交。
数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。
这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。
而数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到的。

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

如果落在低水位左侧部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
如果落在高水位右侧部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
如果落在高低水位之间得部分,那就包括两种情况
	a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
	b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。
InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力
系统中活跃事务ID为99 在以下事务开始前
1.事务A,B,C的版本号分别是100101102,当前系统只有这四个事务
2.三个事务开始前 (1,1)这一行数据row trx_id是90

A 视图数组	[99,100]
B 视图数组	[99,100,101]
C 视图数组	[99,100,101,102](1,1)的执行顺序C->B->A
C/B 写入更新 set k = k+ 1
A读

版本row trx_id
C 读取最新的90版本 创建自己ID 修改生成102的版本(1,2) 90成历史版本
B 读取最新版本102 创建自己ID 修改生成101版本(1,3) 102成历史版本

视图[99,100]
A 查询 CB未提交但是成为了新版本 读数据都是从当前版本开始读
	1.找到当前版本(1,3) row trx_id = 101 比高水位大 查询时事务还未开始 所以不可见
	2.uodo日志找上个版本 发现row trx_id = 102 比高水位大 查询时事务还未开始 所以不可见
	3.undo日志找上个版本 row trx_id = 90 比低水位小 可见
期间这一行数据被修改过,但是事务 A 不论在什么时候查询,看到这行数据的结果都是一致的,所以我们称之为一致性读。
	对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况
	版本未提交,不可见;
	版本已提交,但是是在视图创建后提交的,不可见;
	版本已提交,而且是在视图创建前提交的,可见。

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

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

而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:
	在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
	在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。
	对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
	对于读提交,查询只承认在语句启动前就已经提交完成的数据;
	而当前读,总是读取已经提交完成的最新版本。

问题

由更新逻辑为了满足一致性读而产生的当前读

时间顺序上 
	事务B的set 在事务C的set之后
	但是按照一致性读原则 事务B看不到事务C更改的数据 只能读到事务B的read-view才对 更新视图得到错误的值才对

如果事务 B 在更新之前查询一次数据,这个查询返回的 k 的值确实是1
	它要去更新数据的时候,就不能再在历史版本上更新了,否则事务 C 的更新就丢失了。因此,事务 B 此时的 set k=k+1 是在(1,2)的基础上进行的操作。
	用到了这样一条规则:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。除了 update 语句外,select 语句如果加锁,也是当前读。
	在更新的时候,当前读拿到的数据是 (1,2),更新后生成了新版本的数据 (1,3),这个新版本的 row trx_id 是 101。
	在执行事务 B 查询语句的时候,一看自己的版本号是 101,最新数据的版本号也是 101,是自己的更新,可以直接使用,所以查询得到的 k 的值是 3。

将事务隔离特性和行锁结合

假设情况C没有提交
commit 一致会开卡住 事务B无法执行 事务C不释放行锁 B拿不到 卡死
两阶段锁触发,知道C释放了行锁 B才开始执行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值