可重复读:为什么你改了我看不到呢?

在探索问题之前,先得明白如下知识点

  • InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
  • 而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且 把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。

还没懂的话 就看下面这张图,理解一下

开始探索问题

按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这 个事务执行期间,其他事务的更新对它不可见。 因此,InnoDB 代码实现上,一个事务只需要在启动的时候,找到所有已经提交的事务 ID 的最 大值,记为 up_limit_id;然后声明说,“如果一个数据版本的 row trx_id 大于 up_limit_id,我就不认,我必须要找到它的上一个版本”。当然,如果一个事务自己更新的数据,它自己还是 要认的。有了这个声明后,系统里面随后发生的更新,是不是就跟这个事务看到的内容无关了呢? 因为之后的更新,产生的新的数据版本的 row trx_id 都会大于 up_limit_id。

是不是还是有点懵,举个栗子

我们继续看一下图 中的三个事务,分析下 Q2 语句返回的结果,为什么是 k=1。 这里,我们不妨做如下假设: 1. 事务 A 开始前,系统里面已经提交的事务最大 ID 是 99; 2. 事务 A、B、C 的版本号分别是 100、101、102,且当前系统里没有别的事务; 3. 三个事务开始前,(1,1)这一行数据的 row trx_id 是 90。 这样,事务 A、B、C 的 up_limit_id 的值就都是 99。 为了简化分析,我先把其他干扰语句去掉,只画出了跟 Q2 查询逻辑有关的操作。

从图中可以看到,第一个有效更新是事务 C,把数据从 (1,1) 改成了 (1,2)。这时候,这个数据的 最新版本的 row trx_id 是 102,而 90 这个版本已经成为了历史版本。 第二个有效更新是事务 B,把数据从 (1,2) 改成了 (1,3)。这时候,这个数据的最新版本(即 row trx_id)是 101,而 102 又成为了历史版本。 好,现在事务 A 要来读数据了,它的 up_limit_id 是 99。当然了,读数据都是从当前版本读起 的。所以,Q2 的读数据流程是这样的:

  1. 找到 (1,3) 的时候,判断出 row trx_id=101 大于 up_limit_id,要不起;
  2. 接着,找到上一个历史版本,一看 row trx_id=102,还是要不起;
  3. 再往前找,终于找到了(1,1),它的 row trx_id=90,是可以承认的数据。

这样执行下来,事务 A 读到的这个数据,跟它在刚开始启动的时候读到的相同,所以我们称之 为一致性读。

(1,1) 这个历史版本,什么时候可以被删除掉呢? 答案是,当没有事务再需要它的时候,就可以删掉

同时这也强调了我们不建议使用长事务的原因,回保存多个视图版本从而占据了大量内存。

顺路再说下读提交与可重复读的区别

  • 在可重复读隔离级别下,只需要在事务开始的时候找到那个 up_limit_id,之后事务里的其他 查询都共用这个 up_limit_id;
  • 在读提交隔离级别下,每一个语句执行前都会重新算一次 up_limit_id 的值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值