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

《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 条评论
登录 后参与评论

相关文章

来自专栏张坤的专栏

Unity编译Android的原理解析和apk打包分析

最近由于想在Scene的脚本组件中,调用Android的Activity的相关接口,就需要弄明白Scene和Activity的实际对应关系,并对Unity调用A...

2.7K0
来自专栏数据之美

linux 系统监控、诊断工具之 lsof 用法简介

1、lsof 简介 lsof 是 linux 下的一个非常实用的系统级的监控、诊断工具。 它的意思是 List Open Files,很容易你就记住了它是 “...

3756
来自专栏数据和云

Oracle 12c多租户特性详解:PDB 的出与入 InAndOut

将 Non-PDB 插入 CDB 在12c中,可以将一个非 CDB(也即NON-CDB)插入到 CDB 中,这个过程需要在只读模式下进行。 以下测试首先启动一...

2866
来自专栏闵开慧

修改eclipse中的默认工作路径

首先,关闭Eclipse并打开eclipse的安装目录,依次打开以下目录,configuration\.settings 文件夹下有一个文件 org.eclip...

31310
来自专栏iOS122-移动混合开发研究院

坑中速记整理! 使用 kotlin 写第一个 ReactNative Android 模块

Kotlin 和 Swift, 两大新宠! 借 ReactNative 熟悉下 kotlin 的用法,不料掉坑里面了.昨晚花了大半夜,趁这会儿思路清晰,把涉及到...

24711
来自专栏数据和云

New in 12.2- 让每个PDB都拥有自己的 local undo表空间

12.1引入temp undo概念 ,12.2引入local undo,也就是每个pdb拥有自己的undo表空间(以前版本共用一个undo表空间),rac中每个...

3615
来自专栏Golang语言社区

游戏服务器之多线程发送(下)

5、发送缓冲区数据 检查可发送该线程的相关联的所有会话上的发送缓冲区的数据,检查完后,发送会话上的发送缓冲区的数据。 VOID ExecSockDataMgr:...

3415
来自专栏Golang语言社区

【Golang语言社区】Go语言操作注册表思路

   大家好,我是社区主编彬哥;本篇文章主要是介绍下Go如何操作windows的注册的思路;在windows平台,用cmd命令大家都启动过注册表吧? 先  wi...

3997
来自专栏数据和云

Oracle 12.2 新特性 | PDB不同字符集变更深入解析

从12c版本12.1开始,就有了新特性——Pluggable Database,相比较之前通过schema的管理方式实现多用户管理,Pluggable Data...

3349
来自专栏7号代码

Android开发高级进阶——Service与Notification

Service是处理一些后台任务,在主线程中,并不能执行耗时操作。而Thread是开启一个子线程,在子线程中执行耗时操作,这样不会阻塞主线程。

1124

扫码关注云+社区