首先介绍undo 中的简单结构:
undo header-->transaction table-->undo entries(undo block)-->
undo entries包含的只有一个行改变的col值的undo,并不包含整个行的befor image。
KDO undo record:
KTB Redo op: 0x02 ver: 0x01 op: C uba: 0x02000004.0005.0a
KDO Op code: URP xtype: XA bdba: 0x01c00007 hdba: 0x01c00006
itli: 1 ispac: 0 maxfr: 4863 tabn: 0 slot: 1(0x1) flag: 0x2c
lock: 0 ckix: 0 ncol: 8 nnew: 2 size: -1
col 5: [ 3] c2 20 24
col 7: [ 2] c1 1f
consistent read:
1.读取数据块,如果该块在内存中,则增加一个克隆用于undo
2.读取行头:
tab 0, row 0, @0x1e4c
tl: 44 fb: --H-FL-- lb: 0x1 cc: 8
3.检查lb是否这里有一个itl:
Itl Xid Uba Flag
0x01 xid: 0x0003.000.00000010 uba: 0x02000004.0005.09 ----
4.如果有,则读取这个itl的xid,xid=us#.slot#.wrap#
5.读取交易表,如果这个交易已经提交(state=9表示提交)并且有了一scn小于当前查询的system change number,
那么clean out这个块,继续下一个块(如果需要的话)并且返回第一步。(在每一个查询的时候都将产生一个查询
scn,在该scn一下的commit scn都认为对应的交易已经提交)
TRN TBL:
index state cflags wrap# uel scn dba nub
--------------------------------------------------------------------------
0x00 10 0xc0 0x0010 0x0000 0x0000.00075fec 0x02000004 0x00000001
0x01 9 0x00 0x000f 0x0002 0x0000.00075f77 0x00000000 0x00000000
6.如果没有提交,那么读取最新改变的undo block根据transaction table中显示的dba(该地址为undo entries中最
新改变的块)
7.对比这个块的交易号同交易表中的交易号。如果在undo block中的交易号不等于交易表中的交易号,那么报告错误:
ORA-01555 “Snapshot Too Old.”.
xid: 0x0001.01d.000023a4 seq: 0x2a7 cnt: 0x3f irb: 0x3f icl: 0x0 flg: 0x0000
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
---------------------------------------------------------------------------
0x01 0x1f68 0x02 0x1ee8 0x03 0x1e68 0x04 0x1de8 0x05 0x1d68
0x06 0x1cec 0x07 0x1c6c 0x08 0x1bec 0x09 0x1b6c 0x0a 0x1aec
0x0b 0x1a6c 0x0c 0x19f0 0x0d 0x1970 0x0e 0x18f0 0x0f 0x1874
0x10 0x17f8 0x11 0x1778 0x12 0x16f8 0x13 0x1678 0x14 0x15f8
8.如果这个块的交易号等于交易表中的交易号,那么从这个undo entry的头开始,应用改变到块上(该块为在内存中
克隆的块)
If the Transaction IDs are identical, make a copy of the data block in memory.
Starting with the head undo entry, apply the changes to the copied data block.
9.如果这个undo enties的尾巴上有其他的块地址(因为一个交易可能占用了好几个undo块),那么将这个undo block
读到内存中重复分析第七步和第八步。重复7和8步直到首先的record不在包含其他块的地址。
10.当这里不再有前一个数据块的地址,那么这个交易就完全被undo了。(因为undo 块是从最后一个块往前读的,这
是为了保证在��读的时候,最新改变的数据都能在undo中体现)
11.如果这个undo entry包含:
a):包含一个指向前一个交易undo block的地址,读取这个交易的id号在这个前一个交易的undo block头里并读取相应
的交易表entries,返回第五步。record纪录也是从后往前读的,因此有irb指定先读取哪一个。
* Rec #0x1 slt: 0x00 objn: 48636(0x0000bdfc) objd: 49636 tblspc: 33(0x00000021)
* Layer: 11 (Row) opc: 1 rci 0x00
Undo type: Regular undo Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00801b7c---------------------------------------->>>>>> 这里有一个指针指向前一个undo block证明
这个undo chain上还有一些undo 块。
*-----------------------------
KDO undo record:
KTB Redo
op: 0x02 ver: 0x01
op: C uba: 0x00801b7c.02a7.3e
KDO Op code: URP row dependencies Disabled
xtype: XA bdba: 0x054016c1 hdba: 0x054016b9
itli: 1 ispac: 0 maxfr: 4863
tabn: 0 slot: 85(0x55) flag: 0x2c lock: 0 ckix: 0
ncol: 8 nnew: 5 size: 0
col 3: [ 3] c1 06 36
col 4: [ 3] c1 06 36
col 5: [ 1] 30
col 6: [ 2] c1 02
col 7: [ 6] c4 02 23 38 39 3d
*-----------------------------
* Rec #0x2 slt: 0x00 objn: 48636(0x0000bdfc) objd: 49636 tblspc: 33(0x00000021)
* Layer: 11 (Row) opc: 1 rci 0x01
Undo type: Regular undo Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000------------------------------------------>>>>>>这里的dba因为rci决定了它不是这个block的最
后一个record。
这是因为,如果你查询的表有很多个交易在修改,那也需要读取这些所有的这些交易所涉及的undo block,应用到重做
b):如果包含一个ITL纪录,存储这个ITL record到数据块(cr block),然后返回第四步。
还是关于consistent read。
a.create as select后增加一个表,然后确定当前存放数据的块20没有读到buffer中来:
SQL> select file#,block#,class#,xnc,status,objd,ts# from v$bh where file#>=3 and block#>16;
FILE# BLOCK# CLASS# XNC STATU OBJD TS#
---------- ---------- ---------- ---------- ----- ---------- ----------
3 17 8 0 xcur 6308 3
3 18 9 0 xcur 6308 3
3 19 4 0 xcur 6308 3
b.起一个sqlplus 执行select,注意这个时候在buffer中有一个块缓存了:
SQL> select file#,block#,class#,xnc,status,objd,ts# from v$bh where file#>=3 and block#>16;
FILE# BLOCK# CLASS# XNC STATU OBJD TS#
---------- ---------- ---------- ---------- ----- ---------- ----------
3 17 8 0 xcur 6308 3
3 18 9 0 xcur 6308 3
3 19 4 0 xcur 6308 3
3 20 1 0 xcur 6308 3
c.在一个session中delete50行没有提交,这是产生一个block buffer:
FILE# BLOCK# CLASS# XNC STATU OBJD TS#
---------- ---------- ---------- ---------- ----- ---------- ----------
3 17 8 0 xcur 6308 3
3 18 9 0 xcur 6308 3
3 19 4 0 xcur 6308 3
3 20 1 0 cr 6308 3
3 20 1 0 xcur 6308 3
d.在另外一个session中去执行select动作,有产生了一个block buffer
FILE# BLOCK# CLASS# XNC STATU OBJD TS#
---------- ---------- ---------- ---------- ----- ---------- ----------
3 17 8 0 xcur 6308 3
3 18 9 0 xcur 6308 3
3 19 4 0 xcur 6308 3
3 20 1 0 cr 6308 3
3 20 1 0 xcur 6308 3
3 20 1 0 cr 6308 3
e.设想,c步骤和d步骤中的两个块在buffer中肯定不一样,因为在d步骤中产生的cr block肯定应用了
当前那个块的重做,但是结果是,这个两个块除了scn不相同,其他没有区别。原因是这两个块的copy都是来源于
最初的那个在block buffer中的block,因为前面的一个session删除操作没有提交,因此不需要用undo来产生一致性
读,如果我们并没有在实现select,即block没有事先放到buffer中,两个结果应该完全不同:
f.重新启动数据库,检查一下buffer中没有该块:
g.直接启动一个session开始删除操作:
FILE# BLOCK# CLASS# XNC STATU OBJD TS#
---------- ---------- ---------- ---------- ----- ---------- ----------
3 17 8 0 xcur 6308 3
3 19 4 0 xcur 6308 3
3 20 1 0 xcur 6308 3
3 20 1 0 cr 6308 3
该块被缓存在buffer中有两个块,状态一个为xcur,一个为cr。猜想:xcur为当前修改产生的块,一个为consistent
block,即为cr块。在该session中dump第20块:
scn: 0x0000.0001f0f7 seq: 0x31 flg: 0x00 tail: 0xf0f70631
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump: 0x00c00014
Object id on Block? Y
seg/obj: 0x18a4 csc: 0x00.1d141 itc: 3 flg: E typ: 1 - DATA
brn: 0 bdba: 0xc00011 ver: 0x01
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0xffff.000.00000000 0x00000000.0000.00 C--- 0 scn 0x0000.0001c52b
0x02 0x0004.02a.0000004c 0x00802276.0015.05 ---- 49 fsc 0x0bf6.00000000
0x03 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
我们看到dump出来的肯定是状态为xcur的块,因为这里有一个没有提交的tx。
h.我们开始一个新的session,然后dump这个块:
scn: 0x0000.0001f0f7 seq: 0x31 flg: 0x00 tail: 0xf0f70631
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump: 0x00c00014
Object id on Block? Y
seg/obj: 0x18a4 csc: 0x00.1d141 itc: 3 flg: E typ: 1 - DATA
brn: 0 bdba: 0xc00011 ver: 0x01
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0xffff.000.00000000 0x00000000.0000.00 C--- 0 scn 0x0000.0001c52b
0x02 0x0004.02a.0000004c 0x00802276.0015.05 ---- 49 fsc 0x0bf6.00000000
0x03 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
我们看到这两个块的dump完全一样的。在该session中我们执行一个select操作,然后dump该块,这时我们看到这里
多了一个cr块:
FILE# BLOCK# CLASS# XNC STATU OBJD TS#
---------- ---------- ---------- ---------- ----- ---------- ----------
3 17 8 0 xcur 6308 3
3 19 4 0 xcur 6308 3
3 20 1 0 cr 6308 3
3 20 1 0 xcur 6308 3
3 20 1 0 cr 6308 3
dump出来的文件看看:
scn: 0x0000.0001f280 seq: 0x01 flg: 0x00 tail: 0xf2800601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump: 0x00c00014
Object id on Block? Y
seg/obj: 0x18a4 csc: 0x00.1f280 itc: 3 flg: E typ: 1 - DATA
brn: 0 bdba: 0xc00011 ver: 0x01
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0xffff.000.00000000 0x00000000.0000.00 C--- 0 scn 0x0000.0001c52b
0x02 0x0004.02a.0000004c 0x00802276.0015.05 ---- 49 fsc 0x0bf6.00000000
0x03 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
我们看到scn比原来的大,但是ITL还是一样,然后对比一下这两个不同装载下块的dump,还是一样。
因此虽然我们看到了block buffer中有两个cr块,但是无论你从哪个session中去dump这个块,dump的永远是第���次产生
的cr块。oracle为后来的select产生的cr块,不可会dump出来。
i.我将那个session中的delete提交,因为如果不提交则其他的删除动作因为行被锁住而不能执行,然后执行了一个动作
SQL> delete from test1 where obj#<100;
50 rows deleted.
这个时候cr减少了一个同时,dump出来的块已经到了delete被应用得块了,所以这个cr应该将原来最先的delete执行的
时候的那个cr去掉了,因为提交了一个交易在原始的块上,因此同时产生了一个新的cr块。否则如果所有的交易都不提交
那么cr块永远在增加。
另外对于最后的undo entries上是否有ITL存在,我没有模拟出环境,在原始block上有几个ITL但是,这些已经都是提交的
所以,并不是说上面有几个ITL就要回滚几个,而是根据当前块的scn去检查相应的ITL是否提交,如果小于当前的scn,这
些交易回滚又有什么用呢?
因此还是没有知道最后这个undo entries怎么来的ITL。
*-----------------------------
uba: 0x00802274.0015.37 ctl max scn: 0x0000.0001eb16 prv tx scn: 0x0000.0001eb33
KDO undo record:
KTB Redo
op: 0x04 ver: 0x01
op: L itl: xid: 0x0009.027.0000004b uba: 0x008024d4.0016.37
flg: C--- lkc: 0 scn: 0x0000.0001f04a
KDO Op code: LKR row dependencies Disabled
xtype: XA bdba: 0x00401f4a hdba: 0x00401f49
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 0 to: 0
*-----------------------------
* Rec #0x39 slt: 0x2a objn: 6308(0x000018a4) objd: 6308 tblspc: 3(0x00000003)
* Layer: 11 (Row) opc: 1 rci 0x00
/////这是这个交易在undo中最后��record,他代表的rci已经指向了最前面。
Undo type: Regular undo Begin trans Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000
*-----------------------------
uba: 0x00802274.0015.38 ctl max scn: 0x0000.0001eb33 prv tx scn: 0x0000.0001eb48
KDO undo record:
KTB Redo
op: 0x03 ver: 0x01
op: Z
KDO Op code: IRP row dependencies Disabled
xtype: XA bdba: 0x00c00014 hdba: 0x00c00013
itli: 2 ispac: 0 maxfr: 4858
tabn: 0 slot: 0(0x0) size/delt: 60
fb: --H-FL-- lb: 0x0 cc: 17
null:
以下是这个undo block的头部。
********************************************************************************
UNDO BLK:
xid: 0x0004.02a.0000004c seq: 0x15 cnt: 0x44 irb: 0x44 icl: 0x0 flg: 0x0000
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
---------------------------------------------------------------------------
0x01 0x1f74 0x02 0x1f1c 0x03 0x1ee4 0x04 0x1e74 0x05 0x1e3c
0x06 0x1de4 0x07 0x1d8c 0x08 0x1d1c 0x09 0x1ce4 0x0a 0x1c8c
今天终于让我找到了这个undo entries上包含的前一个itl的信息:
首先增加一张表,设置maxtrans=2;oracle默认的maxtrans=255,因为他只用两位来记录。
连续四次对该表进行操作在不同的交易中,导致该块上的itl被重复使用过:
第一次:
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0008.004.00000315 0x008089c6.0045.16 --U- 9 fsc 0x0000.00153937
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
第二次:
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0008.004.00000315 0x008089c6.0045.16 C--- 0 scn 0x0000.00153937
0x02 0x0008.00d.00000315 0x008089c6.0045.19 ---- 1 fsc 0x0043.00000000
第三次:
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000a.009.0000032b 0x0080387b.0063.2f ---- 1 fsc 0x003a.00000000
0x02 0x0008.00d.00000315 0x008089c6.0045.19 --U- 1 fsc 0x0043.0015397d
第四次:
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000a.009.0000032b 0x0080387b.0063.2f C--- 0 scn 0x0000.001539a0
0x02 0x0006.021.00000314 0x00802b38.009c.48 --U- 1 fsc 0x003f.00153a1e
如此,dump出最后的itl中指示的uba地址找到了undo entries中的recored:
********************************************************************************
UNDO BLK:
xid: 0x0006.021.00000314 seq: 0x9c cnt: 0x48 irb: 0x48 icl: 0x0 flg: 0x0000
...........
*-----------------------------
* Rec #0x48 slt: 0x21 objn: 6379(0x000018eb) objd: 6379 tblspc: 3(0x00000003)
* Layer: 11 (Row) opc: 1 rci 0x00
Undo type: Regular undo Begin trans Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000
*-----------------------------
uba: 0x00802b38.009c.47 ctl max scn: 0x0000.0015032c prv tx scn: 0x0000.0015034f
KDO undo record:
KTB Redo
op: 0x04 ver: 0x01
op: L itl: xid: 0x0008.00d.00000315 uba: 0x008089c6.0045.19
flg: C--- lkc: 0 scn: 0x0000.0015397d
|<--------------------这里就反映了前一个itl------------------------------->|
第三次:
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000a.009.0000032b 0x0080387b.0063.2f ---- 1 fsc 0x003a.00000000
0x02 0x0008.00d.00000315 0x008089c6.0045.19 --U- 1 fsc 0x0043.0015397d
|<--------------------0x02------------------------------------------------>|
因此将这个itl应用到cr块上,接着找该地址就可以undo前一个itl,当前先要对比一下scn号,
到此,consistent reader的这个过程我已经都明白了,而且在实际中显示出来,你明白没有。
KDO Op code: IRP row dependencies Disabled
xtype: XA bdba: 0x00c0000f hdba: 0x00c0000b
itli: 2 ispac: 0 maxfr: 4858
tabn: 0 slot: 2(0x2) size/delt: 65
fb: --H-FL-- lb: 0x0 cc: 17
null:
01234567890123456789012345678901234567890123456789012345678901234567890123456789
-----N-----NN-N--
col 0: [ 2] c1 1f
col 1: [ 2] c1 1f
col 2: [ 1] 80
col 3: [ 7] 49 5f 43 4f 42 4a 23
col 4: [ 2] c1 05
col 5: *NULL*
col 6: [ 2] c1 02
.........
今天差点走入一个误区,我老板说,rbs header中的tx table中的chd和ctl就是记录的关于这个块所有的已经提交的交易。
当时我就表示了怀疑,怎么可能在这里记录,oracle也太傻了。这里当然记录了所有的commit的交易链,那只是说这
个是最老的交易,一个流水号,记录了整个交易链中的顺序,如果重用也是按照最老的开始使用。所以他和consistent
reader用到的undo是无关的。这里也就是biti所说的undo block中也记录了itl的before image。
TRN CTL:: seq: 0x009c chd: 0x001f ctl: 0x0028 inc: 0x00000000 nfb: 0x0001
mgc: 0x8201 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
uba: 0x00802b39.009c.0e scn: 0x0000.001504a5
上面就是transaction header的格式。
dsi原文:
For a transaction ,a transaction slot is allocated as pointed to by CHD.To preserve the data within this
TX slot,the TX commit SCN is saved in KTUXCSCN,the KTUXCUBA field will identify the undo block to protect
the changes to KTUXC,and the CHD and CTL will be updated to reflect the next TX slot to be reused,and
the latest commited slot within the transaction table respectively.thes changes are required to ensure
rollback of the transaction,and read consistency of the TX table header.