前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >看懂MVCC“快照”的工作原理,领导给我升职架构师了

看懂MVCC“快照”的工作原理,领导给我升职架构师了

作者头像
JavaEdge
发布2021-02-22 16:02:32
9160
发布2021-02-22 16:02:32
举报
文章被收录于专栏:JavaEdgeJavaEdge

事务隔离级别提到,如果是可重复读,事务T启动时会创建一个视图read-view。 之后事务T执行期间,即使有其他事务修改了数据,事务T看到的仍然跟在启动时看到的一样。也就是说,一个在可重复读隔离级别下执行的事务,好像不受外界影响。

但是行锁时候又提到,一个事务要更新一行,如果刚好有另外一个事务拥有这一行的行锁,它又不能这么超然了,会被锁住,进入等待。 既然进入了等待,那么等到这个事务自己获取到行锁要更新数据的时候,它读到的值又是啥?

举个例子。下面是个只有两行的表的初始化语句。

  • 事务A、B、C的执行流程

注意事务的启动时机。

begin/start transaction 命令并非事务的起点,在执行到它们之后的第一个操作InnoDB表的语句,事务才真启动。 如果想立即启动事务,可使用

代码语言:javascript
复制
start transaction with consistent snapshot 

没有特别说明,都默认autocommit=1。

事务C没显式使用begin/commit,表示这个update语句本身就是一个事务,语句完成时会自动提交。事务B在更新行后查询; 事务A在一个只读事务中查询,并且时间顺序上是在事务B的查询后。

事务B查到的k的值是3,而事务A查到的k的值是1,你是不是感觉有点晕呢?

在MySQL,有两个“视图”的概念:

  • view 用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。 创建视图的语法是create view … ,而它的查询方法与表一样。
  • InnoDB在实现MVCC时用的一致性读视图consistent read view,用于支持RC(Read Committed)和RR(Repeatable Read)级别实现。 没有物理结构,作用是事务执行期间用来定义“我能看到什么数据”。

今天说明查询和更新区别,把read view拆开。更深一步地理解MVCC。

“快照”在MVCC的工作原理

可重复读下,事务在启动时就“拍了个快照”。该快照基于全库。

你可能觉得不太好吧!如果一个库100G,启动个事务,MySQL就拷贝100G数据,得多慢! 可我平时事务执行好像很快啊!

事实上无需拷贝出这100G的数据。我们先来看看这个快照是怎么实现的。

InnoDB每个事务有个唯一事务ID - transaction id。在事务开始时向InnoDB事务系统申请的,按申请顺序严格递增。

每行数据也都有多个版本。 每次事务更新数据,都会生成一个新的数据版本,并且把transaction id赋值给这个数据版本的事务ID,记row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。即,据表中的一行记录,其实可能有多个版本(row),每个版本有自己的row trx_id。

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

  • 行状态变更图

虚线框是同行数据的4个版本,当前最新版本V4,k值22,是被transaction id 为25的事务更新的,因此它的row trx_id也是25。

前面提过更新语句会生成undo log(回滚日志),它在哪? 图中三个虚线箭头,就是undo log;V1、V2、V3并不是物理上存在的,而是每次需要时根据当前版本和undo log算出的。 比如需要V2时,通过V4依次执行U3、U2算出。

InnoDB怎么定义“100G”快照?

按可重复读定义,一个事务启动时,能够看到所有已提交的事务结果。但之后,这个事务执行期间,其他事务的更新对它就不可见了。

因此一个事务只需在启动时声明:以我启动时刻为准

  • 如果一个数据版本是在我启动前生成的,就认
  • 启动后才生成的,我不认,必须要找到它的上一个版本 若“上个版本”也不可见,那就继续往前找! 如果是这个事务自己更新的数据,自己还是认的。

实现

InnoDB为每个事务构造一个数组,保存这个事务启动瞬间,当前正在“活跃”的所有事务ID

“活跃”:启动了但尚未提交。

数组里面事务ID的最小值记为低水位,当前系统里面已经创建过的事务ID的最大值加1记为高水位。

这视图数组和高水位,就组成为当前事务的一致性视图(read-view)。 而数据版本的可见性规则,就基于数据的row trx_id和这个一致性视图的对比结果得到。

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

  • 数据版本可见性规则

考虑当前事务的启动瞬间,一个数据版本的row trx_id,可能有以下几种情况:

  1. 落在绿色,表明该版本是由已提交的事务当前事务自己生成的,这个数据更新结果自然是可见的
  2. 落在红色,该版本由将来启动的事务生成,不可见
  3. 落在黄色,包括两种情况 a row trx_id在数组,表示这个版本是由还没提交的事务生成,不可见 b row trx_id不在数组,表示这个版本是已经提交了的事务生成,可见

举例,对于上面的行状态变更图中的数据,若有个事务,它的低水位是18,当它访问这行数据时 因为启动后才生成的 tr_id25,所以不认!必须再追溯上个版本: 就会从V4通过U3计算出V3; 而 V3 的 tr_id17,是在启动前生成的,所以认! 所以它看来,这行值11。

有这声明后,系统里随后发生的更新,是不是跟这事务看到的内容无关了? 因为之后的更新,生成的版本一定属于上面的2或者3(a)情况,在它它看来,这些新数据版本都是不存在的,所以这事务的快照,就是“静态”的了。

现在知道了吧,InnoDB利用“所有数据都有多版本”的特性,实现了“秒级创建快照”能力。

接下来,我们继续看一下 图-事务A、B、C的执行流程 中的三个事务,分析事务A语句返回结果,为啥是k=1。

假设:

  1. 事务A开始前,系统里面只有一个活跃事务ID 99
  2. 事务A、B、C版本号100、101、102,且当前系统只有这四个事务
  3. 三个事务开始前,(1,1)这行数据的row trx_id是90。

基于以上假设。可得:

  • 事务A的视图数组[99,100]
  • 事务B的视图数组是[99,100,101]
  • 事务C的视图数组是[99,100,101,102]

为简化分析,把其他干扰语句去掉,只画跟事务A查询逻辑有关操作:

  • 事务A查询数据逻辑图

可得,第一个有效更新是事务C,把数据(1,1)改成(1,2)。 这时数据最新版本的row trx_id是102,90版本成历史版本。

第二个有效更新事务B,把数据(1,2)改成(1,3)。这时数据最新版本(row trx_id)101,102成了历史版本。

注意到了吧!事务A查询时,事务B还没提交呢!但它生成的(1,3)版本已成当前版本。但这版本对事务A必须是不可见的,否则就是脏读啦! 现在事务A要读数据了,它的视图数组[99,100]。当然了,读数据都是从当前版本读起的。所以,事务A查询语句的读数据流程是这样的:

  1. 找到(1,3)时,判断得row trx_id=101,比高水位还大,处红色区,不可见!
  2. 于是,找到上一历史版本,一看row trx_id 102,比高水位大,红色区,不可见!
  3. 再往前,终于找到(1,1),row trx_id 90,比低水位小,处绿色区,可见!

执行下来,虽然期间这一行数据被后启动的事务们修改过了,但事务A不论在何时查询,看到这行数据的结果都一致,所以称之为一致性读。

一个数据版本,对一个事务视图来说,除了自己的更新总是可见外的

简化后的三大判断规则

  1. 版本未提交,不可见
  2. 版本已提交,但却是在视图创建后提交的,不可见:如 BC 之于 A
  3. 版本已提交,且是在视图创建前提交的,可见

用这规则来判断 图-事务A查询数据逻辑 的查询结果,事务A的查询语句的视图数组是在事务A启动时生成,这时:

  • (1,3)还没提交,属情况1,不可见
  • (1,2)虽然提交了,但却是在视图数组创建后提交的,属情况2,不可见
  • (1,1) 在视图数组创建前提交的,可见

去掉数字对比,只用时间先后顺序判断,分析起来轻松多了! 以后就用这套规则分析!

更新逻辑

有人可能疑问:事务B的update语句,如果按照一致性读,好像结果不对哦?

  • 事务B更新逻辑图

看上图,事务B的视图数组先生成,之后事务C才提交,不应该看不见(1,2)吗,怎么能算出(1,3)?

是的,如果事务B在更新前查询一次数据,这查询返回k值就是1。

但当它要更新数据时,就不能再在历史版本上更新了,否则事务C的更新就丢了。 因此,事务B此时的set k=k+1是在(1,2)基础上进行操作。

因此这里就要用到这条规则:更新数据是先读后写,而这里的读,只能读当前值(称为“当前读”(current read))。

因此更新时,当前读拿到数据是(1,2),更新后生成新版本数据(1,3),新版本row trx_id 是 B 写的 101。

所以执行事务B查询语句时,一看自己版本号101,而最新数据版本号也101,是自己的更新,那就可直接使用,所以查询得到的k的值是3。

这里提到的当前读。其实,除update语句,select语句如果加了锁,也是当前读。

所以,如果把事务A查询语句select * from t where id=1改下,加上lock in share modefor update,也可读到版本号101的数据,返回k值3。下面这俩select语句,就是分别加了读锁(S锁,共享锁)和写锁(X锁,排他锁):

代码语言:javascript
复制
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

再往前一步,假设事务C不是马上提交,而变成了下面的事务C’,会咋样?

  • 事务A、B、C’的执行流程

事务C’的不同于之前的C在于,更新后并未立即提交,在它提交前,事务B的更新语句先发起了。 虽然事务C’还没提交,但(1,2)这版本也已生成,并且是当前最新版本。那事务B的更新语句会怎么处理?

这时“两阶段锁协议”上场。 事务C’没提交,即(1,2)这版本的写锁还没释放。 而事务B是当前读,必须读最新版本,且必须加锁,因此就被锁住了,必须等到事务C’释放这锁,才能继续它的当前读。

  • 事务B更新逻辑图(配合事务C’)

这里,一致性读、当前读和行锁就串起了。

回到开头问题:

事务的可重复读能力实现原理

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

而读提交的逻辑和可重复读的逻辑类似,主要的区别是:

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

在读提交下,事务A和事务B查询语句查到的k,分别应该多少?

  • “start transaction with consistent snapshot; ” 从这语句开始,创建一个持续整个事务的一致性快照。所以,在读提交下,这用法就没意义了,等效于普通的start transaction。

下面是读提交时状态图,可看到这两查询语句的创建视图数组时机发生变化,就是图中的read view框。(注意:这里用的还是事务C的逻辑直接提交,而不是事务C’)

  • 读提交隔离级别下的事务状态图

这时,A的查询语句的视图数组是在执行这个语句时创建的,时序上(1,2)、(1,3)生成时间都在创建这个视图数组的时刻前。 但是,在这个时刻:

  • (1,3)还没提交,属于情况1,不可见
  • (1,2)提交了,属于情况3,可见

所以,这时事务A查询语句返回k 2。 显然事务B查询结果k 3。

小结

InnoDB的行数据有多版本。 每个数据版本有自己的row trx_id。 每个事务或者语句有自己的一致性视图。

普通查询语句是一致性读 一致性读根据row trx_id和一致性视图确定数据版本的可见性。

  • 对可重复读,查询只认在事务启动前就已提交完成的数据
  • 对读提交,查询只认在语句启动前就已经提交完成的数据;

当前读,总读取已经提交完成的最新版本。

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

因为表结构没有对应行数据,也没row trx_id,只能遵循当前读逻辑。

当然,MySQL 8.0已经可以把表结构放在InnoDB字典里,也许以后会支持表结构的可重复读。

怎么删除表的前10000行

在一个连接中循环执行20次 delete from T limit 500? 确实这样的,第二种方式相对较好的。

第一种方式,直接delete from T limit 10000 单个语句占用时间长,锁时间也较长;而且大事务还导致主从延迟。

第三种方式,在20个连接中同时执行delete from T limit 500),会人为造成锁冲突。

如果可以加上特定条件,将这10000行天然分开,那就可考虑第三种而不会锁冲突。在操作的时候也建议尽量拿到ID再删除。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-07-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • “快照”在MVCC的工作原理
  • InnoDB怎么定义“100G”快照?
    • 实现
      • 简化后的三大判断规则
      • 更新逻辑
      • 事务的可重复读能力实现原理
      • 小结
      • 为什么表结构不支持“可重复读”?
      • 怎么删除表的前10000行
      相关产品与服务
      云数据库 SQL Server
      腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档