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

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

Block header dump:  0x0200410a
 Object id on Block? Y
 seg/obj: 0x5f07  csc: 0xb08.1303a672  itc: 3  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0001.025.0000472d  0x00000000.0000.00  C---    0  scn 0x0b08.12f461da
0x02   0x0007.015.00004ba0  0x0080d9a0.16f7.39  C-U-    0  scn 0x0b08.12fb5cae
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

我们看看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行数据:

SQL> @mysid

       SID
----------
       160

SQL> create table t1 ( id number, small_vc varchar2(20),padding varchar2(1000)) pctfree 90 pctused 10;

表已创建。

SQL> insert /*+ append */ into t1
  2  select rownum,rownum || lpad('0',10,'0'),lpad('0',1000,'0')
  3  from dba_objects
  4  where rownum< =500;

已创建500行。

SQL>
SQL> commit;

提交完成。

SQL> create table t2 (id number,vc varchar2(20),padding varchar2(1000)) pctfree 90 pctused 10;

表已创建。

SQL> insert /*+ append */ into t2
  2  select rownum,lpad(rownum,20,'0'),lpad(rownum,1000,'0')
  3  from dba_objects
  4  where rownum<=1000;

已创建1000行。

SQL> commit;

提交完成。

SQL> select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid)
  2  from t1
  3  where rownum<=5;

DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------ ------------------------------------
                                   8                                16650
                                   8                                16651
                                   8                                16652
                                   8                                16653
                                   8                                16654

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

SQL> update t1 set padding=lower(padding);

已更新500行。

SQL> select xidusn,xidslot,xidsqn,to_char(start_scnw,'xxxxxxxx') start_scnw,
  2  to_char(start_scnb,'xxxxxxxx') start_scnb,
  3  start_scnb+start_scnw*power(2,32) start_scn from v$transaction;

    XIDUSN    XIDSLOT     XIDSQN START_SCN START_SCN         START_SCN
---------- ---------- ---------- --------- --------- -----------------
         7         21      19360       b08  12f461db    12129305649627

SQL> select file_id,block_id from dba_rollback_segs where segment_name='_SYSSMU7$';

   FILE_ID   BLOCK_ID
---------- ----------
         2        105

SQL> alter system dump datafile 8 block 16650;

系统已更改。

SQL> alter system dump datafile 2 block 105;

系统已更改。

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

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


*** 2012-05-26 11:14:38.439
Start dump data blocks tsn: 8 file#: 8 minblk 16650 maxblk 16650
buffer tsn: 8 rdba: 0x0200410a (8/16650)
scn: 0x0b08.12f461f5 seq: 0x01 flg: 0x00 tail: 0x61f50601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Hex dump of block: st=0, typ_found=1

Block header dump:  0x0200410a
 Object id on Block? Y
 seg/obj: 0x5f07  csc: 0xb08.12f461ce  itc: 3  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0001.025.0000472d  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
0x02   0x0007.015.00004ba0  0x0080d9a0.16f7.39  ----    1  fsc 0x0000.00000000
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

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

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

*** 2012-05-26 11:14:51.052
Start dump data blocks tsn: 1 file#: 2 minblk 105 maxblk 105
buffer tsn: 1 rdba: 0x00800069 (2/105)
scn: 0x0b08.12f46417 seq: 0x01 flg: 0x00 tail: 0x64172601
frmt: 0x02 chkval: 0x0000 type: 0x26=KTU SMU HEADER BLOCK
Hex dump of block: st=0, typ_found=1
.............................
  TRN CTL:: seq: 0x16f7 chd: 0x0008 ctl: 0x002c inc: 0x00000000 nfb: 0x0000
            mgc: 0x8201 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
            uba: 0x0080d9a0.16f7.39 scn: 0x0b08.12eca506
Version: 0x01
  TRN TBL::
 
  index  state cflags  wrap#    uel         scn            dba            parentxid     nub     stmt_num    cmt
  ------------------------------------------------------------------------------------------------
   0x00    9    0x00  0x4bc4  0x0027  0x0b08.12f45e35  0x0080d9a0  0x0000.000.00000000  0x00000002   0x00000000  1338001717
................................
   0x14    9    0x00  0x4c1d  0x001d  0x0b08.12f37a3b  0x0080b31e  0x0000.000.00000000  0x0000009a   0x00000000  1338001345
   0x15   10    0x80  0x4ba0  0x0031  0x0b08.12f461db  0x0080d9e8  0x0000.000.00000000  0x00000049   0x00000000  0
   0x16    9    0x00  0x4bc8  0x002e  0x0b08.12ed360f  0x0080da1e  0x0000.000.00000000  0x0000009a   0x00000000  1338001284
.............
   0x2c    9    0x00  0x4c00  0xffff  0x0b08.12f45f06  0x0080d9a0  0x0000.000.00000000  0x00000001   0x00000000  1338001844
   0x2d    9    0x00  0x4b9d  0x001a  0x0b08.12eec008  0x00000000  0x0000.000.00000000  0x00000000   0x00000000  1338001299
   0x2e    9    0x00  0x4ba0  0x0004  0x0b08.12ed994e  0x0080e51e  0x0000.000.00000000  0x0000009a   0x00000000  1338001287
   0x2f    9    0x00  0x4bbf  0x000c  0x0b08.12f00a3e  0x0080549e  0x0000.000.00000000  0x0000009a   0x00000000  1338001311

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

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

SQL> alter system flush buffer_cache;

系统已更改。

SQL> commit;

提交完成。

SQL> alter system dump datafile 2 block 105;

系统已更改。

看看UNDO段头的转储结果:


*** 2012-05-26 11:20:33.746
Start dump data blocks tsn: 1 file#: 2 minblk 105 maxblk 105
buffer tsn: 1 rdba: 0x00800069 (2/105)
scn: 0x0b08.12f4649c seq: 0x01 flg: 0x00 tail: 0x649c2601
frmt: 0x02 chkval: 0x0000 type: 0x26=KTU SMU HEADER BLOCK
Hex dump of block: st=0, typ_found=1
  TRN CTL:: seq: 0x16f7 chd: 0x0008 ctl: 0x0015 inc: 0x00000000 nfb: 0x0001
            mgc: 0x8201 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
            uba: 0x0080d9a0.16f7.39 scn: 0x0b08.12eca506
Version: 0x01
  index  state cflags  wrap#    uel         scn            dba            parentxid     nub     stmt_num    cmt
  ------------------------------------------------------------------------------------------------
..................
   0x15    9    0x00  0x4ba0  0xffff  0x0b08.12f4649c  0x0080d9e8  0x0000.000.00000000  0x00000049   0x00000000  1338002416
..................

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

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

SQL> @mysid

       SID
----------
       152
SQL> col before_bigtx_scn for 9999999999999999
SQL> select sys.dbms_flashback.get_system_change_number before_bigtx_scn from dual;

 BEFORE_BIGTX_SCN
-----------------
   12129305650318
SQL> set transaction read only;

事务处理集。

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

SQL> @mysid

       SID
----------
       153
SQL> begin
  2    for i in 1..960 loop
  3      update t2 set vc = lpad(i,20,'0');
  4      commit;
  5    end loop;
  6  end;
  7  /

PL/SQL 过程已成功完成。

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

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

*** 2012-05-26 11:27:41.134
Start dump data blocks tsn: 1 file#: 2 minblk 105 maxblk 105
buffer tsn: 1 rdba: 0x00800069 (2/105)
scn: 0x0b08.13039a62 seq: 0x01 flg: 0x00 tail: 0x9a622601
frmt: 0x02 chkval: 0x0000 type: 0x26=KTU SMU HEADER BLOCK
Hex dump of block: st=0, typ_found=1
.....
  
  TRN CTL:: seq: 0x1708 chd: 0x001f ctl: 0x001e inc: 0x00000000 nfb: 0x0001
            mgc: 0x8201 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
            uba: 0x00809aa1.1708.11 scn: 0x0b08.12fb5cae
Version: 0x01
  TRN TBL::
 
  index  state cflags  wrap#    uel         scn            dba            parentxid     nub     stmt_num    cmt
  ------------------------------------------------------------------------------------------------
   0x15    9    0x00  0x4ba2  0x0008  0x0b08.1301d042  0x00809a0d  0x0000.000.00000000  0x00000014   0x00000000  1338002842
   

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

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

SQL> select * from t1 where rownum<=1;
...省略查询表返回的1行结果....
SQL> alter system dump datafile 8 block 16650;

系统已更改。
--转储结果--
*** 2012-05-26 11:29:09.250
Start dump data blocks tsn: 8 file#: 8 minblk 16650 maxblk 16650
buffer tsn: 8 rdba: 0x0200410a (8/16650)
scn: 0x0b08.1303a672 seq: 0x01 flg: 0x00 tail: 0xa6720601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump:  0x0200410a
 Object id on Block? Y
 seg/obj: 0x5f07  csc: 0xb08.1303a672  itc: 3  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0001.025.0000472d  0x00000000.0000.00  C---    0  scn 0x0b08.12f461da
0x02   0x0007.015.00004ba0  0x0080d9a0.16f7.39  C-U-    0  scn 0x0b08.12fb5cae
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

由于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行数据所在的数据块,得到的内容如下:

Block header dump:  0x0200410a
 Object id on Block? Y
 seg/obj: 0x5f07  csc: 0xb08.1303a702  itc: 3  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0001.025.0000472d  0x00000000.0000.00  C---    0  scn 0x0b08.12f461da
0x02   0x0007.015.00004ba0  0x0080d9a0.16f7.39  C---    0  scn 0x0b08.12f4649c
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

从上面的结果可以看到,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语句的统计数据:

Name                                                               Value
--------------------------------------------------------------------------------
STAT...physical read IO requests                                       1
STAT...data blocks consistent reads - undo records applied             1
STAT...transaction tables consistent read rollbacks                    1
STAT...immediate (CR) block cleanout applications                      1
STAT...commit txn count during cleanout                                1
STAT...redo entries                                                    1
STAT...cleanout - number of ktugct calls                               1
STAT...db block changes                                                1
STAT...table scans (short tables)                                      1
STAT...enqueue requests                                                1
STAT...enqueue releases                                                1
STAT...physical read total IO requests                                 1
STAT...cursor authentications                                          1
STAT...session cursor cache hits                                       1
STAT...table scan blocks gotten                                        1
STAT...table scan rows gotten                                          1
STAT...physical reads                                                  1
STAT...physical reads cache                                            1
STAT...cleanouts and rollbacks - consistent read gets                  1
STAT...CR blocks created                                               2
STAT...shared hash latch upgrades - no wait                            2
STAT...execute count                                                   2
STAT...parse count (total)                                             2
STAT...opened cursors cumulative                                       2
STAT...SQL*Net roundtrips to/from client                               3
STAT...calls to kcmgas                                                 3
STAT...calls to get snapshot scn: kcmgss                               3
STAT...free buffer requested                                           3
STAT...user calls                                                      4
STAT...DB time                                                         8
STAT...transaction tables consistent reads - undo records ap          60
STAT...consistent changes                                             61
STAT...consistent gets - examination                                  61
STAT...consistent gets from cache                                     66
STAT...consistent gets                                                66
STAT...session logical reads                                          66
STAT...redo size                                                     116
STAT...bytes received via SQL*Net from client                        571
STAT...bytes sent via SQL*Net to client                            1,777
STAT...physical read total bytes                                   8,192
STAT...physical read bytes                                         8,192

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如下:

Block header dump:  0x0200410a
 Object id on Block? Y
 seg/obj: 0x5f0b  csc: 0xb08.1327da77  itc: 3  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0007.028.00004bca  0x00000000.0000.00  C-U-    0  scn 0x0b08.1326548d
0x02   0x0003.02c.00004c00  0x008097ab.159c.03  C-U-    0  scn 0x0b08.1326501e
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

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

Block header dump:  0x0200410a
 Object id on Block? Y
 seg/obj: 0x5f0b  csc: 0xb08.1327da89  itc: 3  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0007.028.00004bca  0x00000000.0000.00  C-U-    0  scn 0x0b08.1324ce8c
0x02   0x0003.02c.00004c00  0x008097ab.159c.03  C-U-    0  scn 0x0b08.1324d090
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

上面两次列出的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。
,
Trackback

8 comments untill now

  1. 很认真的看了一遍,非常好!

  2. 太详细了,great!

  3. 熊老,我执行alter system flush buffer_cache;后发现转储出来的undo slot还是10的状态并且数据block的itl也未显示提交状态,除非进行checkpoint,11.2.0.1。

  4. deepblue @ 2012-08-28 18:22

    认真看了,很不错,果然是高手,有深度。

  5. Wavelet123 @ 2013-03-19 11:10

    我的理解是,做延迟块清除的时候,只是标记一下,所以itl是C-U,如果需要构造CR块,就会得到准确的SCN,前提是undo事务表还能回滚。

  6. 看了好几遍,很好,但是还有个疑问,会话2是怎么找到精确的scn的,用redo恢复undo,然后回滚undo一直回滚到事务提交的时候的scn?

  7. netbanker @ 2013-04-11 08:16

    11g那个undo guarantee不知道能不能避免both undo block and undo block slot overwritten的情况

    老熊 Reply:

    可以解决这个问题。但是undo表空间的空间大小会是个问题,如果空间不够,事务不能进行引起的可用性问题可是比较严重。

Add your comment now