加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

常见关系库MVCC浅析1

(2022-03-31 00:08:12)
分类: 工作学习
本文作为常见关系库MVCC浅析的第一篇,将汇总常见的单机关系库产品的MVCC实现,包括Oracle、postgresql和mysql。本文会大量应用网上内容,相关出处汇总在文末的参考章节。

OracleMVCC的实现

OracleMVCC也是基于回滚段来实现的,即历史版本并不作为业务数据文件的一部分,而是作为undo日志的一部分。

Oracle的数据行中基本没有存储事务信息,只存储了一个锁信息,每个数据行有个LB位,如果为0那么说明这行数据没有被锁定,否则LB存储的是这个行锁所对应的事务在ITLInterest transaction list)中的slot编号。ITL位于数据块头部,它包含了这个数据块的在进行或已经完成的事务信息,一个进行中的事务必然占用一个slot,而一个完成的事务对应的slot可以被一个新事务覆盖,slot的数量是有限的。

ITL主要包含了:

1. Slot编号,第一列

2. Xid,代表对应的事务idtransac[X]tion identified),在回滚段事务表中有一条记录和这个事务对应。Xid3列使用十六进制编码的数字列表示,分别是:Undo Segment Number + Transaction Table Slot Number + Wrap,即由undo段号+undo槽号+undo槽号的覆盖次数三部分组成,即usn.slot.sqn。通过Xid可以找到undo回滚段对应段头的事务表。因为Oracle有快速提交和延迟提交技术,只有undo回滚段头中的事务表中的事务状态是最准确的,ITL和数据行中的都可能是不准的(不准主要是针对这个事务是否已经提交,如果ITL中是未提交,那么就需要检查对应的undo回滚段头中的事务表)。如果是快速提交,那么在提交的时候,会将事务表和每一个数据块的ITL槽进行清除。但是锁定标记可能没有清除,等下次用到的时候再进行清除。如果是延迟提交,那么在提交的时候,只是将事务表进行清除,并没有对ITL事务槽进行清除,每一行的锁定标记也没有清除。

3. Uba,即Undo Block Address,该事务对应的回滚段地址,记录了最近一次的该记录的前镜像(修改前的值)。Uba组成:Undo块地址(undo文件号和数据块号)+回滚序列号+回滚记录号。多版本一致读是Oracle保证读操作不会被事务阻塞的重要特性。需要注意,回滚段数据部分除了保存这个事务对这个数据块修改相关行的前镜像以外,还存储了这个事务执行时候的数据块ITL,通过存储ITL,实现了历史版本的串联。

4. Flag,是事务标志位,即当前事务槽的状态信息。这个标志位记录了这个事务的操作状态。如果没值表示这个事务可能还在执行(需要再检查undo端头的事务表),有值则表示事务状态,例如“C”表示事务已经提交,锁已经被清除。

5. Lck,表示这个事务所影响的行数,锁住了几行数据,对应有几个行锁。

6. Scn/FscCommit SCN或者快速提交(Fast Commit Fsc)的SCN


常见关系库MVCC浅析1

    Oracle为每一个事务的第一条语句分配一个SCN号,基于这个scn进行事务一致性读。事务开始的时候同步会分配回滚段,并在回滚段的头部的事务表中分配一个slot,记录事务状态。

    一致性查询的时候会检查对应数据块ITL中的最大SCN号,如果小于一致性读的SCN,那么可以直接读取这个事务块,否则从最大的SCN号的UBA找到对应的回滚段历史版本,检查历史版本中的ITL,看是否最大SCN小于一致性读SCN,依次类推,知道找到一个版本,然后基于当前数据库应用这一链路的历史版本,生成一个CRconsistent read)块。


PostgreSQLMVCC实现

PostgreSQLMVCC实现与Oracle基于回滚段的实现有一个非常大的差异,就是PG的历史数据是直接存储在业务数据文件中的,是业务数据文件的一部分。在PG的数据页中,每一数据行是一个Tuple,数据行中除了业务数据外在数据行header部分还会有一些系统字段:

常见关系库MVCC浅析1

(本图片引用自网络,出处见文末“参考”)

xmin 创建该tuple时的事务ID(后续都称为CreationID),由INSERTUPDATE操作时设置

xmax 过期的事务ID(后续都称为ExpiredID),在删除该tuple时,记录此时的事务ID,或者更新这个tuple时在旧的tuple上记录更新操作时的事务ID;如果该值不为0,则说明该行数据进行过删除或更新或回滚

cmincmax 标识在同一个事务中增删改元组时存储其序列值,从0开始,只有INSERT/UPDATE/DELETE操作会递增,用于同一个事务中实现版本可见性判断

ctid 是用来记录当前元组或新元组的物理位置,有块号和快内偏移组成,可以用来查找元组的下一个版本在磁盘上的位置,如果这个元组被更新,则该字段指向更新后的新元组,所以一个元组是最新版本,当且仅当它的xmax为空或者它的ctid指向它自己。

t_infomask表明了xminxmax的状态(committed/invalid/aborted)等信息,而t_infomask2包含了该元组属性的个数(即表的列数)和其他一些标志信息

       下图是一个示例,一个数据行在update语句执行前tuple1是最新版本数据,update语句执行后,新生成了tuple2数据行。Tuple2xminupdate事务的id,而xmax0,表示这行数据还未被删除。而历史版本Tuple1xmax改为了1835,是这行数据的结束事务id。而Tuple1ctid列也指向了这行历史版本数据的下一个版本所在位置。同一个事务内如果对同一行修改多次,那么也会生成多个tuplexmax都是当前事务id,事务内计数编号会累加。

常见关系库MVCC浅析1
                                              (本图片引用自网络,出处见文末“参考”)


PGMVCCInnodb类似的地方是,它也需要继续活跃事务快照判定数据行的可见性。事务快照组成为:“xmin:xmax:xip_list”。事务快照的创建过程主要如下:

a、查看所有未提交并活跃的事务记录到数组xip_list

b、选取数组中这些事务中最小的XID存储到该快照的xmin字段;

c、选取数组中这些事务中最大的XID1后存储到该快照的xmin字段;

常见关系库MVCC浅析1

                                              (本图片引用自网络,出处见文末“参考”)


MVCC过程中将基于快照来判断一个数据行当前事务是否可见。对于同一个业务数据的不同历史版本事务行,任何一个事务只会看到其中的一个版本。

MVCC的可见性判定的基本思路为通过判定一行记录的xminxmax对应的事务是否对当前事务快照是否已完成来判定一个数据行是否可见。如果xmin已经完成,但xmax还未完成,那么这个行就可见。

具体的判定逻辑为:

常见关系库MVCC浅析1

同一个行的多个历史版本都会被扫描。

Rule7为例,如果xmin已经提交,而xmax对应的事务还在运行中,而tmax是当前事务,那么这个行是这个事务内之前修改的行,不应该被看见。

Rule9为例,如果xmin已经提交,而xmax对应的事务状态是提交,tmax不是当前事务,并且这个事务还在当前事务快照中(即事务其实还在运行),那么说明这个行目前操作的终结事务id未提交,这个数据行还是可见的。

Rule10为例,如果xmin已经提交,而xmax对应的事务状态是提交,tmax不是当前事务,并且这个事务不在当前事务快照中(即事务真的已经提交了),那么这个行目前操作的终结事务id已经提交,即这个行已经被删除或废弃,这个数据行不可见。

上述rule中的status的判定可能会涉及到行t_infomask系统列和pgpg_clog文件(记录事务的提交状态,是最准确的)。行t_infomask系统列的作用类似pg_clog的缓存,如果它的值是提交,那么就是准确的,如果不是提交,就需要再检查pg_clog文件。和Oracle的快速提交和延迟提交类似,pgt_infomask系统列不是在事务提交的时候更新,而是后续被操作的时候更新,例如再查询到这个行的时候。


MySQL InnoDBMVCC实现

MySQL InnoDBMVCC与Oracle类似,都是基于回滚段存储历史版本,但不同的是InnoDB是行级别的,而Oracle是块级别的。

InnoDB的每个行都有2个系统隐藏字段DB_TRX_ID和DB_ROLL_PTR,前者是事务ID,后者是指向回滚段上一个历史版本的指针,一个历史版本可能还会通过DB_ROLL_PTR指向更早的版本。

InnoDB与Oracle不同,Oracle提交的时候会分配并存储Commit SCN,而InnoDB只存储了事务开始时分配的事务ID,所以InnoDB引入了活跃事务快照来做可见性判定。

基本的思想就是只能看见已经提交的最新版本,所有当前事务t启动或运行的时候还未开启或正在执行的事务t对于t是不可见的。

活跃事务列表是维护在innodb全局trx_sys中的一个列表,即当前活跃事务的列表;而活跃事务快照是对活跃事务列表的一个瞬时拷贝,它是一个左闭右开的区间,up_limit_id:low_limit_id:ids。

Up_limit_id是活跃事务列表事务id的最小值,所有小于它的事务都是已提交可见的事务。Low_limit_id是全局要分配的下一个id,所有大于等于它的事务都是当前事务开始时还未启动的事务,都是不可见事务。而在Up_limit_id和low_limit_id之间的事务,如果在ids列表中的为不可见事务。

   InnoDB不记录事务的提交id,所以也没有Oracle或PG的回填开销。

常见关系库MVCC浅析1
                                              (本图片引用自网络,出处见文末“参考”)



参考:

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有