今天在一个帖子里,csucxcc朋友推荐了一篇很不错的文章。虽然有些地方没有看懂,呵呵,可就是感觉到它很不错!生活中有的时候就是这样,不知道算不算第六感。转到这里来以后自己有空了就多看看,读书百遍,其义自见嘛。顺便也和各位朋友分享分享。
事务和数据块
这里主要想通过试验来推断Oracle的事务在数据块(Oracle中存储的最小单位)上一些微观操作,以便于更好的理解Oracle的事务控制、一致性处理及锁冲突。
创建测试表:
SQL> create table t_multiver (a number, b number);
Table created.
SQL> insert into t_multiver values (1,1);
1 row created.
SQL> insert into t_multiver values (2,2);
1 row created.
SQL> insert into t_multiver values (3,3);
1 row created.
SQL>
SQL> commit;
Commit complete.
首先,看一下数据块上和记录有关的标示,dump一个数据块看看:
SQL> alter system dump datafile 5 block 50816;
System altered.
看看Dump出来的内容:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4462b75 itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0008.020.0000192f 0x0080051a.059f.34 --U- 3 fsc 0x0000.a4462b7a
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
data_block_dump,data header at 0x9f75664
===============
tsiz: 0x1f98
hsiz: 0x18
pbl: 0x09f75664
bdba: 0x0140c70f
76543210
flag=--------
ntab=1
nrow=3
frre=-1
fsbo=0x18
fseo=0x1f7d
avsp=0x1f65
tosp=0x1f65
0xe:pti[0] nrow=3 offs=0
0x12:pri[0] offs=0x1f8f
0x14:pri[1] offs=0x1f86
0x16:pri[2] offs=0x1f7d
block_row_dump:
tab 0, row 0, @0x1f8f
tl: 9 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 02
col 1: [ 2] c1 02
tab 0, row 1, @0x1f86
tl: 9 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 03
col 1: [ 2] c1 03
tab 0, row 2, @0x1f7d
tl: 9 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 04
col 1: [ 2] c1 04
end_of_block_dump
End dump data blocks tsn: 5 file#: 5 minblk 50959 maxblk 50959
注意红色黑体部分和Interested Transaction Slot (ITS) 部分。
CSC:即Cleanout SCN,最后一次对该数据块进行Cleanout的SCN。
Itc: Itl count。Itl的数量。看到下面的Itl列表中有两条记录,所以是2。
Itl:Interested Transaction List。直译过来就是感兴趣的事物列表。也就是对该数据块产生影响的事物。对应的数字是序列号。
Xid
:Transaction ID。事务ID号。在相应回滚段的事物信息表中对应唯一一条记录。
Uba:Undo Block Address。回滚数据块地址,该地址对应了该事务在回滚段中记录的回滚数据的地址。
分解该地址:0x0080051a.059f.34
第一段是回滚数据块的地址,包括回滚段文件号和数据块号;第二段是回滚序列号;第三段是回滚记录号。
Flag:事务标志位。这个标志位就记录了这个事务的操作,各个标志的含义分别是:
C--- = transaction has been committed and locks cleaned out
-B-- = this undo record contains the undo for this ITL entry
--U- = transaction committed (maybe long ago); SCN is an upper bound
---T = transaction was still active at block cleanout SCN
Scn/Fsc:快速提交(Fast Commit Fsc)的SCN或者Commit SCN。
每条记录中的行级锁对应Itl条目lb,对应于Itl列表中的序号,即那个事务在该记录上产生的锁。
回滚段
下面再看看回滚段中是如何记录的:
更新数据(数据更新前b=115):
SQL> conn demo/demo
Connected.
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> update t_multiver set b=116 where a=1;
1 row updated.
查看事务信息:
select UBAFIL, UBABLK, UBASQN, UBAREC from v$transaction;
11 80 886 16
UBAFIL:回滚段文件号;
UBABLK:数据块号;
UBASQN:回滚序列号;
UBAREC:回滚记录号。
这些数据就对应了数据块中的Uba。
也可以通过以下方式从数据块的Uba中获取到回滚段这些信息:
0x02c00050.0376.10
0376即回滚序列号,10即回滚记录号。它们都是16进制。
下面获取文件号和数据块号:
SQL> set serveroutput on
SQL> declare
2 v_file number;
3 v_block number;
4 begin
5 v_file := dbms_utility.data_block_address_file(to_number('02c00050','xxxxxxxx'));
6 v_block := dbms_utility.data_block_address_block(to_number('02c00050','xxxxxxxx'));
7 dbms_output.put_line('file id: '||v_file);
8 dbms_output.put_line('block id: '||v_block);
9 end;
10 /
file id: 11
block id: 80
PL/SQL procedure successfully completed.
Dump出数据块和回滚块:
SQL> alter system dump datafile 5 block 50959;
System altered.
SQL> conn demo/demo
Connected.
SQL>
SQL> alter system dump datafile 11 block 80;
System altered.
数据块dump信息:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a39eda itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000f.006.00000a14 0x02c00050.0376.10 ---- 1 fsc 0x0000.00000000
0x02 0x0012.02a.0000096d 0x02c00088.0355.09 C--- 0 scn 0x0000.a4a39bd8
回滚块信息:
********************************************************************************
UNDO BLK:
xid: 0x000f.006.00000a14 seq: 0x376 cnt: 0x10 irb: 0x10 icl: 0x0 flg: 0x0000
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
---------------------------------------------------------------------------
0x01 0x1f68 0x02 0x1ed0 0x03 0x1e50 0x04 0x1d94 0x05 0x1d2c
0x06 0x1cd4 0x07 0x1c7c 0x08 0x1c24 0x09 0x1ba4 0x0a 0x1b24
0x0b 0x1a8c 0x0c 0x1a0c 0x0d 0x1998 0x0e 0x1918 0x0f 0x187c
0x10 0x17e8
... ...
*-----------------------------
* Rec #0x10 slt: 0x06 objn: 58476(0x0000e46c) objd: 58476 tblspc: 5(0x00000005)
* 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: 0x02c00050.0376.0f ctl max scn: 0x0000.a4a39a02 prv tx scn: 0x0000.a4a39a16
txn start scn: scn: 0x0000.a4a39eda logon user: 35
prev brb: 46137423 prev bcl: 0
KDO undo record:
KTB Redo
op: 0x04 ver: 0x01
op: L itl: xid: 0x0014.022.00000776 uba: 0x02c000a5.029b.32
flg: C--- lkc: 0 scn: 0x0000.a4a39a3e
KDO Op code: URP row dependencies Disabled
xtype: XAxtype KDO_KDOM2 flags: 0x00000080 bdba: 0x0140c70f hdba: 0x0140c70b
itli: 1 ispac: 0 maxfr: 4858
tabn: 0 slot: 0(0x0) flag: 0x2c lock: 0 ckix: 8
ncol: 2 nnew: 1 size: 0
Vector content:
col 1: [ 3] c2 02 10
End dump data blocks tsn: 11 file#: 11 minblk 80 maxblk 80
仔细比较一下两个dump文件,数据块的事务回滚信息就很清楚了:
在数据块中:
对象号是0xe46c
,即十进制的58476;
Xid是0x000f.006.00000a14
;
Uba是0x02c00050.0376.10
。即11号文件的第80个数据块,回滚序列号是0x0376,回滚记录号是0x10。
回滚数据块中:
Xid是0x000f.006.00000a14
,与数据块中Itl中的Xid对应;
seq: 0x376
,与数据块的Uba中吻合;
cnt: 0x10
,此回滚数据块中有16条回滚记录,根据Uba,我们要找的是第16条记录;
找到第16条回滚记录Rec #0x10
:
slt: 0x06
,即第6个slot。
objn
和objd
是58476
,与数据块中的对象号相同;
tblspc
是5
,也就是对象t_multiver所在的表空间号;
Layer
即层次,这是一次对记录的更新,所以是11 (Row)
;
Undo type: Regular undo Begin trans
,我们这个事务是刚开始时,在没有提交或回滚时dump出来的;
Temp Object: No
,这个对象不是临时对象。
在*-----------------------------以下的就是记录的旧数据镜像了:记录了它的Uba、最大控制Scn(ctl max scn
)、上一次事务scn(prv tx scn
)等信息,如果我们做了事务开始前的数据dump,就能一一找到对应关系了。最下面就是修改前的数据记录了。
快速提交
当一个事物访问一个数据块时,它会占用数据块Itl中的一个条目,记录下事务ID(Xid)、该事务对该数据块操作时使用的回滚块地址(Uba)和在该数据块上产生的行级锁的数量,并在对应数据行上打上行锁标志,与Itl对应。当提交时,设置Itl中的事物标志为U,并写入一个快速提交SCN(Fsc),但并不清除锁标志。这就是快速提交。
看下面的一个试验。
先对数据块进行修改,然后dump出数据块:
SQL> conn demo/demo
Connected.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> update t_multiver set b=115 where a=1;
1 row updated.
SQL>
SQL> commit;
Commit complete.
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> alter system dump datafile 5 block 50959;
System altered.
看看dump出来的内容:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a48c25 itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000f.006.00000a14 0x02c00050.0376.10 C--- 0 scn 0x0000.a4a39ef0
0x02 0x0013.003.00000a58 0x02c0008f.039a.0a --U- 1 fsc 0x0000.a4a48c26
... ...
block_row_dump:
tab 0, row 0, @0x1f5e
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f4a
tl: 10 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] c2 03 17
... ...
可以看到,事务在Itl中记录下了Xid、Uba、Flag(U)、锁、Fsc;并且记录上的锁标志没有清除。
延迟块清除
从上面内容可以知道,事务在数据块上作的操作并没有清除产生的锁标志和其他标志(检测锁冲突),这些标志的清除需要留到下一个访问该数据块的事务去做清除。下一个访问到该数据块的事务会先检查自己要访问的数据行上有没有锁,如果都没有,就继续作自己的操作,如果记录上有锁,就做一次清除(Cleanout)。看下面的试验:
这个试验是接着上面的,但是这修改的是数据块中的另外一条记录:
SQL> conn demo/demo
Connected.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> update t_multiver set b=115 where a=2;
1 row updated.
SQL>
SQL> commit;
Commit complete.
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> alter system dump datafile 5 block 50959;
System altered.
看看dump出的数据块:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a48c25 itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000e.008.000007f2 0x02c0003f.02ca.24 --U- 1 fsc 0x0000.a4a48df3
0x02 0x0013.003.00000a58 0x02c0008f.039a.0a --U- 1 fsc 0x0000.a4a48c26
... ...
block_row_dump:
tab 0, row 0, @0x1f5e
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f4a
tl: 10 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] c2 02 10
tab 0, row 2, @0x1f54
... ...
这个事务并没有清除上一事务的锁标志,并且还产生了自己的锁标志,因为他们之间没有锁冲突。从这个试验,我们还可以得出以下结论:事务是按照数据块中的记录处理的,不是按照Itl中的条目处理的。
而块清除可以分为两种情况。一种是针对快速提交的。如果快速提交后,脏数据回写到磁盘上,此时数据块的锁标志还没有清除,后面访问该数据块的事务(只是产生数据更新的事务,不包括读事务),会先清除(Cleanout)上一个快速提交事务留下的锁标志(同时也是做锁判断),然后开始事务操作。看看试验:
这里继续接着上面的试验,再对数据块做一次修改操作,并且修改的记录上已经有锁标志,看看后面事务是否清除了前面事务的标志:
SQL> conn demo/demo
Connected.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> update t_multiver set b=115 where a=2;
1 row updated.
SQL>
SQL> commit;
Commit complete.
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> alter system dump datafile 5 block 50959;
System altered.
看看dump出的内容:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a4918e itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000e.008.000007f2 0x02c0003f.02ca.24 C--- 0 scn 0x0000.a4a48df3
0x02 0x0014.007.000007a2 0x02c000a5.02ab.23 --U- 1 fsc 0x0000.a4a4918f
... ...
block_row_dump:
tab 0, row 0, @0x1f5e
tl: 10 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f4a
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] c2 02 10
tab 0, row 2, @0x1f54
... ...
可以看到,这个事务对前面的事务作了一次Cleanout,所有相关标志都被修改。
另一种情况是,事务开始后,会给数据块打上锁标志。但是事务标志和SCN号是在事务提交后写入的,如果在事务提交前,DBWn进程已经将数据回写到磁盘,那么数据块上就没有这些信息。后面访问该数据块的事务(包括所有读写事务)需要先根据Xid和Uba从回滚段的事务信息表中获取到这些信息,然后作一次Cleanout。再看试验:
事务提交前回写数据块:
SQL> update t_multiver set b=115 where a=1;
1 row updated.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> commit;
Commit complete.
SQL>
SQL> alter system dump datafile 5 block 50959;
System altered.
看看数据块内容:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a49c85 itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000e.00a.000007f5 0x02c0003f.02cc.1e ---- 1 fsc 0x0000.00000000
0x02 0x000d.028.00000a2b 0x02c00035.039b.03 C--- 0 scn 0x0000.a4a49c71
... ...
block_row_dump:
tab 0, row 0, @0x1f5e
tl: 10 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f4a
... ...
可以看到,Flag和scn没有被记录,而Xid、Uba和锁标志都已经被记录。这里还可以推断出:Xid和Uba在事务开始,获取到空闲Itl后是最先被记录的;锁标志是针对记录一一被记录的;Flag和Scn是事务提交后被记录的。
看看这种情况下,是如何做Cleanout的。我们这不再选用更新事务,而是做一个读操作:
SQL> alter system dump datafile 5 block 50959;
System altered.
SQL> select * from t_multiver;
A B
---------- ----------
1 115
2 115
3 222
SQL>
SQL> alter system dump datafile 5 block 50959;
System altered.
看Dump内容:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a49e6b itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000e.00a.000007f5 0x02c0003f.02cc.1e C--- 0 scn 0x0000.a4a49c89
0x02 0x000d.028.00000a2b 0x02c00035.039b.03 C--- 0 scn 0x0000.a4a49c71
... ...
block_row_dump:
tab 0, row 0, @0x1f5e
tl: 10 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f4a
... ...
可以看到,前面事务的标志都被Cleanout了。
回滚
如果事务一旦回滚,则会清除数据块上的所有标志,就不存在延迟块清除了。
还是看看试验吧,回更清楚一些。
先做更新操作,在提交/回滚前dump:
SQL> conn demo/demo
Connected.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> update t_multiver set b=115 where a=1;
1 row updated.
SQL>
SQL> alter system dump datafile 5 block 50959;
System altered.
看看数据块上的变化:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a49e6b itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0012.01b.000009ae 0x02c0007c.0372.27 ---- 1 fsc 0x0000.00000000
0x02 0x000c.017.00000a25 0x02c0001f.0396.21 C--- 0 scn 0x0000.a4a4a07b
... ...
block_row_dump:
tab 0, row 0, @0x1f5e
tl: 10 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f4a
... ...
注意到Xid、Uba和Lck标志已经被打上,但是Flag和SCN还没有。
然后回滚事务:
SQL> rollback;
Rollback complete.
SQL>
SQL> alter system dump datafile 5 block 50959;
System altered.
再看数据块内容:
Block header dump: 0x0140c70f
Object id on Block? Y
seg/obj: 0xe46c csc: 0x00.a4a49e6b itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x140c709 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000e.00a.000007f5 0x02c0003f.02cc.1e C--- 0 scn 0x0000.a4a49c89
0x02 0x000c.017.00000a25 0x02c0001f.0396.21 C--- 0 scn 0x0000.a4a4a07b
... ...
block_row_dump:
tab 0, row 0, @0x1f5e
tl: 10 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f4a
... ...
所有锁标志被清除,Itl都已经被还原(这个测试是接着前面的进行的,可以对比前面dump出来的内容,发现Xid、Uba和Scn是一样的,所以说是“还原”)。
一致性读
当一个读操作读到某一个数据块上,发现该数据块有被修改过的痕迹(事务标志、锁标志),会需要通过SCN来检查是读取当前数据还是修改前的数据,以保证读取到的所有数据都是读操作开始发生那一时刻(快照)的数据来保证数据的一致性。如果数据块上事务的SCN<读操作的SCN,说明数据块上的更改是发生在读操作之前的(所有SCN是唯一的,并且是累加的),就可以直接读取数据块上修改过的数据;否则,就需要从回滚段中读取修改前的数据,以保持数据的一致性。这就是一致性读。
下面是一个一致性读的例子:
SQL> select * from t_multiver;
A B
---------- ----------
1 115
2 115
3 222
SQL> var cc refcursor
SQL>
SQL> begin
2 open :cc for select * from t_multiver;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL>
SQL> update t_multiver set b=115 where a=3;
1 row updated.
SQL> print :cc
A B
---------- ----------
1 115
2 115
3 222
SQL> select * from t_multiver;
A B
---------- ----------
1 115
2 115
3 115
从这个例子可以看到,读取操作只会读取读取查询开始时那一时刻的数据,如果数据在读取操作以后发生了变化,则从回滚段中读取老数据镜像。如果修改事务已经提交,并且在读取操作读到该数据块时,回滚段中的信息已经被覆盖,则会发生1555错误。我另外一篇文章介绍了1555错误,这就不再赘述了。