当前位置: 首页 > 科技观察

InnoDB二级快照原理及当前读

时间:2023-03-21 01:16:11 科技观察

如果是可重复读隔离级别,事务T启动时会创建视图read-view,然后在事务T执行过程中,即使其他事务修改了数据,事务T将看到的仍然与启动时看到的相同。也就是说,在可重复读隔离级别下执行的一个事务,仿佛与世无争,不受外界影响。但是,我在MySQL锁机制一文中介绍《探究MySQL锁机制》的时候,一个事务需要更新一行。如果恰好有另外一个事务拥有了这一行的行锁,它就会被加锁,进入等待状态。问题来了,既然进入了等待状态,那么事务获取行锁更新数据时读取的值是多少呢?两种启动事务的方式需要注意的是:begin/starttransaction命令并不是事务的起点,真正启动事务的是它们执行后的第一条操作InnoDB表的语句之后。如果你想立即开始一个事务,你可以使用具有一致快照命令的事务!第一种启动方式,一致性视图是在第一个snapshotread语句执行时创建的;第二种启动方法,一致性视图是在执行starttransactionwithconsistentsnapshot时创建的;事务C没有显式使用begin/commit,说明update语句本身就是一个事务,语句执行完会自动提交。事务B更新行后查询;事务A在只读事务中查询,按时间顺序在事务B的查询之后。但是结果是:事务B查到的k的值为3,事务A查到的k的值是1。按照我们的想象,情况应该是这样的:最终的结果就是查询的结果事务A是1,事务B查询结果是2,为什么事务B查询后是3??需要注意的是,在MySQL中,视图有两个概念:1.一个是视图,它是由查询语句定义的视图调用时执行查询语句并生成结果的虚表。创建视图的语法为createview…,其查询方法与表相同。2、另一种是InnoDB在实现MVCC(Multi-VersionConcurrencyControl,多版本并发控制)时使用的一致性读视图,即一致性读视图,用于支持RC(ReadCommitted,读提交)和RR(RepeatableRead,可重复读)隔离级别实现。InnoDB创建二级快照的原理本文第一张图是MVCC的实现原理。在可重复读隔离级别下,事务在启动时“拍摄快照”。请注意,此快照基于整个库。如果一个库有100G,那我启动一个事务,MySQL就拷贝100G的数据。这个过程有多慢。但是通常的交易执行得非常快。其实这100G的数据我们是不需要拷贝的。我们来看看这个快照是如何实现的:InnoDB中的每个事务都有一个唯一的事务ID,称为事务ID。在事务开始时应用到InnoDB事务系统中,严格按照应用的先后顺序递增。每行数据也有多个版本。事务每更新一次数据,都会产生一个新的数据版本,将事务id赋值给这个数据版本的事务ID,记录为rowtrx_id。同时,要保留旧的数据版本,在新的数据版本中,可以有可以直接获取的信息。也就是说,数据表中的一行记录实际上可能有多个版本(即行中的每条数据),每个版本都有自己的行trx_id。如图所示,是一条记录被多个事务不断更新后的状态。图中三个虚线箭头为undolog(回滚日志);V1、V2、V3在物理上并不存在,而是在每次需要时根据当前版本和undolog计算出来的。比如需要V2时,通过V4依次执行U3、U2计算。根据可重复读的定义,当一个事务开始时,可??以看到所有提交的事务结果。但是后来,在这个事务的执行过程中,来自其他事务的更新对它是不可见的。所以事务只需要在启动的时候声明,“以我启动的那一刻为准,如果在我启动之前生成了一个数据版本,我就认;如果是在我启动之后生成的,我就不认它。”,我得去找它以前的版本”。如果是事务自己更新的数据,它还是要识别的。在实现上,InnoDB为每个事务构造一个数组,保存所有当前已经启动但在事务启动的时刻还没有提交的事务ID。数组中交易ID的最小值记录为低水位,当前系统中已经创建的交易ID的最大值加1记录为高水位。视图数组和高水位线组成了当前事务的一致性视图(read-view),数据版本的可见性规则是根据数据的行trx_id和一致性视图进行比较得到的。这个view数组把所有的rowtrx_id分为几种不同的情况:这样,对于当前事务开始的那一刻,rowtrx_id的一个数据版本有以下几种可能:1.如果落在绿色部分,说明这个版本是由提交的事务或当前事务本身生成的,并且此数据是可见的;2、如果落在红色部分,说明这个版本是由一个未来要开始的交易生成的,肯定是不可见的;3.如果落在黄色部分,则包括两种情况。如果trx_id这一行在数组中,说明这个版本是由一个还没有提交的事务生成的,不可见;如果trx_id这一行不在数组中,说明这个版本是由一个已经提交的事务生成的是的,可见。重点是理解落在黄色部分的两种情况。如果一条数据被多个事物更新(事务还没有提交),那么它必须在数组中包含rowtrx_id,即这个版本是由还没有提交的事务产生的。那么InnoDB如何在二级创建快照呢?我的总结如下:1.当事务开始时,InnoDB会给事务分配一个ID。为了方便起见,我们直接称之为交易ID。2.表中每一行数据都有多个版本。会产生一个新的数据版本,将A事务的事务ID赋值给这个数据版本的事务ID3,通过回滚日志计算出事务ID对应的数据版本。例如A事务更新id=1数据的值为1,则有id=1数据的新版本,(id=1,value=1)此数据对应的事务ID为A事务的事务ID4,如果一个数据版本的事务ID落在黄色部分并且还在事务数组中,则判断这个数据版本是其他未提交的事务对数据修改产生的新版本数据;如果一个数据版本的事务ID落在黄色部分,不在自己的事务数组中,那么事务已经提交,是合法数据所以InnoDB的秒级快照创建能力的原理无非就是撤回linuxAUFS文件系统,就是你只要改了某条数据,我就把那条拷贝过来给你改,不影响别人。数据:都是“电流读数”造成的。当我们了解了InnoDB如何创建二级快照的原理之后,一开始的疑惑其实很容易解答:我们不妨做以下假设。在事务A开始之前,只有一个活跃的事务ID为99;事务A、B、C的版本号分别为100、101、102,当前系统只有这4个事务;三个事务开始前,第(1,1)行数据如果trx_id为90,则事务A的视图数组为[99,100],事务B的视图数组为[99,100,101],而事务A的视图数组为事务C是[99,100,101,102]。这其实很容易理解。事务C发现此时有四个事务已经开启但未提交(99、100、101、102)。由于其他事务还没有修改id=1的数据,此时只有一个版本为(id=1,k=1,事务ID=90),事务ID90处于低水位,说明即事务ID为90的事务已经提交,数据有效,然后事务C将k设置为2,并提交事务,所以此时生成了一个新的版本(id=1,k=2,事务ID为102)。最后,事务A查询k的值,发现已经有3个版本。当事务A看到最新版本(id=1,k=3,事务ID为101)时,发现101处于高水位,无法读取,然后读取同一个事务102的时候versionis(id=1,k=2,transactionIDis102)也是高水位,无法读取,继续读取,读取version(id=1,k=1,transactionID)的数据为90),交易ID为90的交易处于低水位,数据可见,所以获取到的数据仍然是(id=1,k=1,交易ID为90)的数据版本。事务B如何看到事务C的修改结果?对于事务B,事务C不是高水位线吗??理论上高水位交易的修改应该是不可见的!!!先生成事务B的视图数组,再提交事务C。(1,2)不应该是不可见的吗?我们如何计算(1,3)来吧?是的,如果事务B在更新前查询过一次数据,这次查询返回的k值确实是1。但是,当它要更新数据时,不能再在历史版本上更新,否则事务的更新C会丢失。因此,此时事务B的集合k=k+1是基于(1,2)的操作。所以,这里使用了这样一个规则:update数据先读后写,而这次读只能读到当前值,称为“当前读”。因此更新时,当前读取的数据为(1,2),更新后会生成新版本的数据(1,3),而这个新版本的行trx_id为101。因此,在执行查询时事务B的语句,可以看到自己的版本号是101,最新数据的版本号也是101,是自己更新的,可以直接使用,所以查询得到的k值为3.但是select的结果是1,所以我们团队对一开始的例子做了一点修改,让更新的时候这个值不是k=k+1,避免当前读取,所以下面的例子就是resultoftheavoidedcurrentread结果:因为在MYSQL中:同表查询的数据不能作为同表的更新数据,所以我这里使用临时表。其实除了update语句,如果select语句被锁住,当前也是读取的。现在假设事务C没有立即提交,而是变成了后面的事务C',会发生什么?事务C'的不同之处在于,它不是在更新后立即提交的。在提交之前,事务B的更新语句首先被发起。前面说了,事务C'虽然还没有提交,但是版本(1,2)也已经生成了,而且是最新的版本。那么,事务B的更新语句会怎样呢?事务C'还没有提交,也就是说版本(1,2)上的写锁还没有释放。而事务B是当前读,必须读到最新版本,必须加锁,所以被加锁,必须等到事务C’释放锁后才能继续当前读。至此,我们连接了一致性读、当前读和行锁。可重复读可重复读的核心是一致性读(consistentread);当事务更新数据时,只能使用当前读取。如果当前记录的行锁被其他事务占用,则需要进入锁等待。readcommit的逻辑和repeatableread类似。主要区别在于,在可重复读隔离级别下,只需要在事务开始时创建一个一致视图,然后事务中的其他查询共享这个一致视图。性观;在read-committed隔离级别下,每条语句执行前都会重新计算一个新视图。InnoDB行数据有多个版本,每个数据版本都有自己的行trx_id,每个事务或语句都有自己的一致视图。普通查询语句是一致性读,根据行trx_id和一致性视图来决定数据版本的可见性。对于可重复读,查询只识别事务开始前已经提交的数据;对于读提交,查询只识别语句开始前已经提交的数据;而对于当前读取,它总是读取最新的已提交和完成的数据版本。作者:zchanglin链接:https://juejin.cn/post/6951281035401232415来源:掘金版权归作者所有。商业转载请联系作者授权,非商业转载请注明出处。