【深度好文】有关延迟块清除和一致性读

《ORA-1555错误解决一例》一文中,当时尝试模拟UNDO段头事务表被覆盖的情况下出现ORA-01555错误,没有成功。实际上没有成功的原因是事务数虽然多,但是事务过小,使UNDO块没有被覆盖完,这样通过回滚事务表仍然能够得到事务表以前的数据。本文进一步讨论一些有关延迟块清除和一致性读方面的内容.

先来看看一个数据块中ITL的转储:

我们看看ITL中的第2个条目,其Flag为"C-U-",C表示commit(提交),U表示Upper bound(上限),这里”U“表明ITL中记录的事务的提交SCN并不是精确的,只是表明事务提交的精确SCN是在ITL中记录的SCN之前,对于第2条ITL来说,其事务提交SCN小于或等于”0x0b08.12fb5cae“。

那么这里的问题是:Upper bound是在什么情况下出现的?如果一个SQL语句对该块进行一致性读时,发现ITL中的Upper bound的SCN比一致性读需要的SCN大,这时会发生什么?

要回答这些问题,先来看下面的一系列测试过程:

1. 在会话1中建测试表t1,将插入500行数据,每个块只有1行数据,一共500个块,然后再创建一个较大的测试表t2,插入1000行数据:

表已创建。

2. 在会话1中更新测试表T1中的所有行,并获取事务ID,然后再dump1个数据块和事务对应的UNDO段头块:

事务使用的事务表在回滚段_SYSSMU7$上,即第7个回滚段。事务表中的条目为21,即事务表中的第21条记录。

数据块dump出来的结果是(去掉了对本文话题无关紧要的内容,以后也是如此):

可以看到ITL中的第2条正是当前活动事务在这个块上所使用的ITL。

Xid为“0x0007.015.00004ba0”,转换成10进制正是“7.21.19360”,这跟之前查询出来的事务ID是一致的。ITL中此时的flag为"----",正是活动事务的标志。由于块中只有1行数据,因此Lck为1,即该事务在这个块中锁住的行数为1行。

下面再来看看此时UNDO段头块的转储结果:

可以看到index为0x15(即10进制21)的行,其wrap#为0x4ba0,与事务的xid相符,同时其state为10,表示事务是活动的,而事务表项上的scn值也是跟之前从v$transaction中查询出来的值是完全一致的。

3. 在会话1中将所有的block刷出内存,然后提交,这样T1表中的所有块上的事务都不会被清除,再将UNDO段头dump出来:

看看UNDO段头的转储结果:

从上面的内容可以看到,state已经变为9,表示事务已经提交,提交SCN为0x0b08.12f4649c,而UNDO段头中的“TRN CTL”中的“ctl: 0x0015”表示这一条事务表记录将会是该事务表中SCN最大的一条,即会最后被选择重用。

4. 现在我们在会话2中,使用只读事务来模拟开始长时间的查询,其查询时间点就是只读事务设置时当前的时间点:

5. 然后在会话3里面,发起960次事务,但是每个事务都很少:

由于我测试的数据库,UNDO表空间中的UNDO段有10个(同时全部都是处于online状态),每个UNDO段头上的事务表有48条记录(在10g中8K大小的块其固定为48条,随着版本和块大小的不同,事务表的大小有变化)。所以960次事务,事务表中的每条记录平均被重用2次,而由于事务较小,UNDO表空间足够大,那么UNDO块不会被覆盖完,即之前会话1上对T1表所做的事务产生的UNDO是不会被覆盖的。

6. 在会话1中,然后转储之前T1表的事务使用的UNDO段头块,得到的内容如下:

可以看到,这里seq从原来的0x16bf变成了0x1708,即事务表中的记录用了73次,看来在各个UNDO段头之间的事务表的使用并不完全是平均的。

7. 在会话1中,查询T1表中的1条记录,然后再转储T1表上第1行数据的数据块:

由于T1表之前的事务在块上的信息并没有清除,因此毫无疑问应该会进行延迟块清除,从上面ITL的数据来看,第2条,也就是之前的事务所使用的那条ITL,Flag已经是"C-U-“,同时Lck为0,表示事务已提交。那么事务提交的SCN是多少呢,这里的SCN是0x0b08.12fb5cae,由于Flag中有"U",表示这个SCN只是一个上限值,这里记录的SCN并不是精确的事务提交SCN,只是表明事务的SCN会比小于等于现在记录的这个SCN值。

那么这个值是怎么来的?这正是之前提出的其中一个问题。

可以确定的是,这个块的延迟清除是由会话1发起的,现在描述一下这个过程:会话1查询T1表,很显然这个时候T1表的事务早已经提交,所以我们知道它不是需要回滚表块上的数据的;但是程序就是程序,它只能按照逻辑来进行操作,它检查块的ITL列表时,发现有一个活动事务(由于之前的事务没有清除),所以它通过ITL记录的xid去检查事务是否已经提交,它从UNDO段头事务表的第0x15条记录发现,该记录的wrap#是0x4ba2,并不是事务的seq号0x4ba0,因此这条记录已经被重用,那么可以确认这条记录已经提交,需要进行事务延迟块清除;那么事务的提交SCN是多少呢?

由于事记录表中的记录已经被重用,所以不能直接得到;接下来会话会从UNDO段头的"TRN CTL::"部分得到一个SCN值:0x0b08.12fb5cae,这个SCN是事务表中最近被重用的事务表记录之前的事务的提交SCN,由于事务表记录重用是按照提交SCN从小到大的顺序重用的,所以可以说这个SCN比事务表中目前所有记录的事务的提交SCN都要小,但它又是这个事务表中曾经被重用过的事务记录的提交SCN中的最大值,由于我们要查看的事务记录已经被重用过,所以很显然该事务的提交SCN会小于或等于"TRN CTL"记录的SCN。

基于这个原理,会话1知道,ITL中需要清除的事务小于等于UNDO段头中TRN CTL记录的SCN,而这个SCN值已经比它的一致性读SCN要小,所以会话1足够判断不需要回滚该事务就能得到一致性读结果,所以这个时候,它就直接将TRN CTL中的SCN作为ITL中的事务提交SCN的上限值,并将Flag设置为"C-U-"。这里会话1不需要回滚事务表就已经足够,它不会做多余的工作来得到精确的事务提交SCN,只需要能判断事务提交时间小于一致性读的SCN就可以了。

8. 在会话2中,执行下面的SQL:

SQL> select * from t1 where rownum<=1;

由于会话2在会话3的大量事务之前就设置了一致性读的起点(通过设置事务为只读模式),很显然,这个时间点比上述第7个操作步骤中会话1的查询时间要早得多,也在上面提到的上限SCN 0x0b08.12fb5cae要小。那么此时会发生什么?

9.在会话1中再一次转储T1表第1行数据所在的数据块,得到的内容如下:

从上面的结果可以看到,SCN也变成了 0x0b08.12f4649c,这正是T1表上之前的事务提交的准确的SCN,事务Flag从原来的”C-U-“变成了“C---”也说明了现在的SCN是精确的事务提交SCN。这说明了什么?这说明当会话2在读到这个块时,虽然发现事务已经提交,但是其记录的SCN是个上限值,并且大于其一致性读时间点,所以它还需要进一步来查找该事务更接近精确的提交SCN。这个时候就会要回滚事务表,

实际上,由于在T1表的事务提交之后而在会话2的查询时间点之前这段时间内,在这个UNDO段中没有事务产生,所以会话2在回滚事务表到最后,就刚好发现了事务的精确提交SCN。那么,这里得到一个重要结论是:如果会话发现ITL中的事务有比现在记录的上限SCN有更精确或更小的提交SCN时,会用新找到的SCN替换到ITL中现在记录的SCN。也同样是一个修改块的操作,也可以认为是一次“块清除”。下面的统计数据是会话2执行SELECT语句的统计数据:

immediate (CR) block cleanout applications值为1表示有1次逻辑读产生的块清除,还有"transaction tables consistent reads - undo records application"表示产生了事务表的回滚。

接下来,我们再做一个测试,具体的测试数据不再列出,测试表仍然是T1和T2,步骤如下:

  1. 会话1更新表T1,将buffer cache中的块全部刷出内存,然后再提交。
  2. 会话1发起960次小事务更新表T2。
  3. 会话2执行set transaction read only。
  4. 会话1发起960次小事务更新表T2。
  5. 会话3执行set transaction read only。
  6. 会话1发起960次小事务更新表T2。
  7. 会话2查询表T1。
  8. 会话3查询表T2。

上面步骤的第7步完成后,数据块的ITL如下:

第8步完成后,数据块的ITL如下:

上面两次列出的ITL中的第2个条目是我们要关注的,可以看到,会话2查询T1表之后,Flag为”C-U-“,记录的SCN为0x0b08.1326501e,会话3查询T1表之后,Flag仍然为”C-U-“,SCN发生了变化,为0x0b08.1324d090,这个SCN值更小(实际上第1个ITL条目也在变化),但仍然不是精确的提交SCN。

上面的测试这也证明了本文得到的两个结论:

  1. 在一致性读时进行块清除,并不需要得到事务的精确提交SCN(尽管它通过回滚事务表可能会得到精确的提交SCN),只需要它发现事务的提交SCN比自己的一致性读的时间点小就可以了。
  2. 如果在一致性读时发现了更小的(更接近精确提交的)SCN值或精确的提交SCN值,它还会做一次块清除,修改ITL,以记录更小或更精确的SCN。

原文发布于微信公众号 - 数据和云(OraNews)

原文发表时间:2017-05-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逸鹏说道

SQL Server 索引和表体系结构(包含列索引)

包含列索引 概述 包含列索引也是非聚集索引,索引结构跟聚集索引结构是一样,有一点不同的地方就是包含列索引的非键列只存储在叶子节点;包含列索引的列分为键列和非键列...

3048
来自专栏好好学java的技术栈

jdbc就是这么简单

JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问...

492
来自专栏沃趣科技

事件记录 | performance_schema全方位介绍

在上一篇 《配置详解 | performance_schema全方位介绍》 中,我们详细介绍了performance_schema的配置表,坚持读完的是真爱,也...

41712
来自专栏大内老A

使命必达: 深入剖析WCF的可靠会话[编程篇](上)

在《实例篇》给出的例子中,我实际上是通过对终结点的绑定进行相应的配置让整个消息的交换过程在一个可靠会话中进行,进而实现可靠消息传输的目的。由于整个可靠会话的机制...

1705
来自专栏乐沙弥的世界

基于Innobackupex的增备及恢复

    MySQL的热备(物理备份)可以采取全备加增量备份的方式来减轻数据库I/O压力及系统资源的占用。增量备份主要是以全备或增量备份为基础,备份那些变更过的页...

502
来自专栏积累沉淀

kafka自学之路--zookeeper中存储结构

? 1.topic注册信息 /brokers/topics/[topic] : 存储某个topic的partitions所有分配信息 Sch...

2117
来自专栏跟着阿笨一起玩NET

SQL Server 2008 新语法

331
来自专栏沈唁志

谈谈在SQL语句中的优化技巧

1534
来自专栏菩提树下的杨过

C#中Dispose和Close的区别!

当我们开发C#代码的时候,经常碰到一个问题,有些class提供Close(),有些class提供Dispose(),那么Dispose和Close到底有什么区别...

1728
来自专栏linux驱动个人学习

udev实现热插拔

一、UDEV是什么? Udev是一个针对Linux内核2.6的可提供自动创建的设备节点和命名的解决方法的一个文件系统;其实与/etc/目录下的fstab文件类似...

3304

扫描关注云+社区