Mysql 事务隔离(二)

场景

  • 可重复读隔离级别,在启动事务时创建一个视图read-view;之后事务执行期间,即使其他事务修改了数据,本事务看到的仍然和启动时看到的一样。
  • 一个事务要更新一行,如果刚好有另一个事务拥有这一行行锁,会造成锁等待。
  • 问题:当这个事务拿到锁的时候,他读到的数据到底是什么呢?

事务启动

  • begin或start transaction时并不是真正的起点,而是其后面第一个操作InnoDB表的语句,事务才真正启动;可重复读隔离级别下,第一个快照读语句会创建视图。
  • 如果想要马上启动事务,可以使用start transaction with consistent snapshot,此时直接创建视图。
  • 快照读:读取的是快照版本,也就是历史版本。普通的SELECT就是快照读。
  • 当前读:读取的是最新版本。UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是当前读。
  • 图片来自Mysql45讲08 , 初始k=1在这里插入图片描述
  • 结果B事务读到的是3,A事务读到的是1;因为A的视图是在更新前创建,B的视图是在select时创建。

视图

  • 一个是view,是用查询语句定义的的虚拟表,create view…
  • 第二个是InnoDB在实现MVCC(多版本并发控制)时用到的一致性读视图,即consistent read view,用于支持读提交和可重复读隔离级别的实现。作用是事务执行期间用来定义能看到什么数据

快照

  • 在可重复读隔离级别下,事务在启动时拍了个快照,这个快照是基于整个库的。
  • 问题:假设库有100G,快照不是复制,那是怎么实现的呢?

实现方法

  • InnoDB里面每个事务有一个唯一的事务ID,叫做transaction id。是在事务开始时向InnoDB事务系统申请的,是按照申请顺序严格递增的。
  • 每行数据是有多个版本的,每次事务更新数据的时候,都会生成一个新的数据版本。并且把transaction id赋值给这个数据版本的事务ID,记为row trx_id。旧版本保留。所以数据库中的一行记录,可能有多个版本,每个版本的row trx_id就是更新出这个版本的事务的transaction id。
  • 图片来自Mysql45讲08
    在这里插入图片描述
  • 语句更新会生成回滚日志(undo log),即图中的虚线箭头。
  • V1、V2、V3并不是真实存在的的,而是需要当前版本和回滚日志计算出来的。比如,需要V2时是通过V4依次执行U3和U2得到的。
  • 按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果,但是之后,这个事务执行期间,其他事务的更新对它不可见。
  • 因此一个事务只需要在启动时声明说,以我启动的时刻为准,如果数据版本是在我启动前生成的,就认;否则就不认,必须找到它的上一个版本。
  • InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在活跃的所有事务ID(数组最后一个值是本事务id),活跃指的就是,启动了还没提交。数组里面事务ID的最小值记为低水位,当前系统里面创建过的事务ID最大值加1记为高水位。
  • 这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。
  • 而数据版本的可见性规则,就是基于数据的row trx_id和这个一致性视图的对比结果得到的。
  • 图片来自Mysql45讲08在这里插入图片描述
  • 这样对于当前事务的启动瞬间来说,一个数据版本的row trx_id(即更新出这个数据版本的事务transaction id),有以下几种可能;
  • 落在绿色部分,该数据版本可见,因为在启动前就提交了。
  • 落在红色部分,肯定不可见。
  • 落在黄色部分,若row trx_id在数组中,表示该版本是由本事务启动瞬间,那些还未提交的事务生成的,当然不可见。
  • 落在黄色部分,若row trx_id不在数组中,表示该数据版本是由在低水位事务后申请,在本事务启动前就已经提交,所以可见。
  • InnoDB利用了“所有数据都有多个版本”这个特性,实现了秒级创建快照的能力。

解答

  • 为什么 A 读到1 ?

  • 设A,B,C的transaction id分别是 100,101,102。A的视图数组是[100],即只有他自己。低水位是100,高水位是101

  • C事务更新的数据版本是102,B事务更新的数据版本是101

  • A的读取流程是先从当前版本读起,即101版本,判断大于等于高水位,位于红色区,不可见。再读数据的上一版本,102,大于高水位,依旧不可见。在读之前一版本,即更新k=1的事务,row trx_id肯定小于100,可见。

  • 对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:版本未提交,不可见;版本已提交,但是是在视图数组创建后提交的,不可见;版本已经提交,且是在视图创建前提交的,可见。

更新逻辑

  • 更新时,不能在历史版本更新,否则事务C的更新就丢失了;
  • 更新数据都是先读后写的,这个读只能读当前值,因此B更新时当前读拿到的k=2,再更新得到k=3,这个k=3版本的 row trx_id 是101
  • 所以B查询时,B的视图数组是,[100,101]。低水位100,高水位102。读当前版本是101,该版本是由自己更新,所以认了。
  • 如果A使用select … lock in share mode(读锁)或select … for update(写锁),也属于当前读,也是可以读到版本号是101的数据即k=3。

总结

  • InnoDB 的行数据有多个版本,每个数据版本有自己的 row trx_id,每个事务或者语句有自己的一致性视图。
  • 可重复读的核心是一致性读;事务更新数据时,只能使用当前读,如果当前记录的行锁被其他事务占用,就需要进入锁等待。
  • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图。
  • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。
  • 普通查询语句是一致性读,一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性。
  • 对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
  • 对于读提交,查询只承认在语句启动前就已经提交完成的数据;
  • 而当前读,总是读取已经提交完成的最新版本。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值