1. Oracle中的事务:
SQL> select xid,xidusn,xidslot,xidsqn,ubablk,ubafil from v$transaction;
XID XIDUSN XIDSLOT XIDSQN UBABLK UBAFIL
---------------- ---------- ---------- ---------- ---------- ----------
0200160000020000 2 22 512 2968 2
SQL> select * from v$rollname where usn=2;
USN NAME
---------- ------------------------------
2 _SYSSMU2$
xid表示事务ID,xidusn表示事务undo semgment number, xidslot表示事务槽,xidsqn表示sequence(覆盖次数),
其实xid有:xidusn, xidslot, xidsqn三部分组成。
ubablk表示undo block address block_no, 即回滚块编号;
ubafil表示undo block address file_no,即回滚块所在的文件编号;
我们知道每一个数据块在块头部有两个重要的内容:事务槽,行目录。
事务槽用于保存修改该数据块的事务的相关信息。
SQL> select ini_trans,max_trans from dba_tables where table_name='TEST_INDEX';
INI_TRANS MAX_TRANS
---------- ----------
1 255
事务槽的数量初始值为1,最多可以有255个.
事务槽的争用:
每一个修改该数据块的事务,都需要占用一个事务槽。只有当事务提交时,该事务占用的事务槽才能被覆盖。我们知道pctfree=10%,那么当多个事务来修改该数据块时,需要多个事务槽,但是可能没有了空间,就必须等待其他的事务提交,新的事务才能开始。这就是所谓的事务槽的争用。而多个事务并行运行一些insert语句时,为了避免事务槽争用,可以才起插入到不同的数据块来减少事务槽的争用。但是并行事务的delete和update导致的事务槽争用却没有方法减少。
根据上面的信息我们可以对 回滚块和回滚段块头进行dump,然后进行研究:
alter system dump undo header '_SYSSMU2$';
alter system dump datafile 2 block 2968;
下图是一个数据块dump得到的头部的ITL事务槽:
Itl: 事务槽编号;
xid: 事务ID;其中包含了xidusn, xidslot, xidsqn,所以xid指向了事务表的一个条目;
uba: 回滚块地址;(数据块中的uba在构造CR块时使用;而事务表中的uba在rollback时使用)
flag: 事务是否提交的标识;用于实现行级锁;
lck:该事务锁定了几行数据;
scn: 前面我们知道每一条日志记录有一个scn;
注意:上面的ITL中的事务信息,同时也存在于undo段的事务表中;
快速提交:普通的提交一般要修改多个地方:undo段的事务表、数据块头部的事务槽、数据行头部的标记等等。但是快速提交是:当一个事务涉及到要修改很多个数据块时,为了避免要在每个数据块头部都进行修改,而仅仅只修改undo段的事务表的提交标识。所以只有undo段的事务表中的提交信息才是最准确的。
select语句也可能产生redo log:当select语句扫描到一行,发现行头部有锁标识,然后找到该行对应的事务槽中的事务信息,如果提交标识没有显示提交,然后再根据xid找到事务表,如果其中的事务信息,显示该事务已经提交,则select语句会将事务槽中的提交标识和数据行头部的锁标识分别设置为提交和null。所以就产生了redo log信息。
行级锁:数据块中的每一行数据的头部有一个事务槽编号的字段,当该行被一个事务修改时,则在该字段填上该事务对应的在事务槽中的ITL编号,来表示该行被锁定了。如果该字段为null则该行没有被锁定。当然,如果该字段不为null时,也不能肯定该行被锁定了,还要看事务槽中的提交标记,甚至还要看事务表中的提交标识。因为快速提交时,有可能只修改事务表中的提交标识。
创建一个测试表格:
SQL> create table t2(id number, name varchar2(32));
Table created.
SQL> insert into t2 values(1, 'a');
1 row created.
SQL> insert into t2 values(2, 'b');
1 row created.
SQL> commit;
Commit complete.
开始一个事务,产生了行锁:
SQL> update t2 set name='abc' where id=2;
1 row updated.
然后换一个session执行下面的操作,避免因为执行一些语句而将update语句的事务字段提交了。
SQL> select t2.*,dbms_rowid.rowid_relative_fno(rowid) fno,dbms_rowid.rowid_block_number(rowid) bno from t2;
ID NAME FNO BNO
---------- -------------------------------- ---------- ----------
1 a 1 64370
2 b 1 64370
将数据块dump出来:
SQL> alter system dump datafile 1 block 64370
System altered.
SQL> select spid from v$session s inner join v$process p on s.paddr=p.addr inner join
v$mystat m on s.sid=m.sid and m.statistic#=0;
SQL> show parameter user_dump;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------------
user_dump_dest string /u01/app/oracle/admin/jiagulun/udump
[oracle@localhost udump]$ pwd
/u01/app/oracle/admin/jiagulun/udump
[oracle@localhost udump]$ ls *29879*
jiagulun_ora_29879.trc
[oracle@localhost udump]$ cat jiagulun_ora_29879.trc
... ...
Block header dump: 0x0040fb72
Object id on Block? Y
seg/obj: 0xcfa7 csc: 0x00.103367 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0003.006.00000219 0x0080011f.01b7.24 C--- 0 scn 0x0000.00103250
0x02 0x0004.015.000001e7 0x00800261.01a1.27 ---- 1 fsc 0x0000.00000000
data_block_dump,data header at 0xcc2b45c
===============
tsiz: 0x1fa0
hsiz: 0x16
pbl: 0x0cc2b45c
bdba: 0x0040fb72
76543210
flag=--------
ntab=1
nrow=2
frre=-1
fsbo=0x16
fseo=0x1f62
avsp=0x1f77
tosp=0x1f77
0xe:pti[0] nrow=2 offs=0
0x12:pri[0] offs=0x1f98
0x14:pri[1] offs=0x1f62
block_row_dump:
tab 0, row 0, @0x1f98
tl: 8 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 02
col 1: [ 1] 61
tab 0, row 1, @0x1f62
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] 61 62 63
end_of_block_dump
End dump data blocks tsn: 0 file#: 1 minblk 64370 maxblk 64370
EXEC #3:c=15997,e=16422,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=1319661474282543
=====================
我们将 update t2 set name='abc' where id=2 语句的数据块出来。从dump出来的数据可以看出:
1) lb: 0x0 表示第一行数据头部的锁定标志为0,所以没有被锁定;
2)lb: 0x2 表示第二行数据头部的锁定标志为2,它表示ITL事务槽的第二条事务信息;而第二条事务信息
的flag为空,表示没有提交,所以该行被锁定了(当然我们开需要查看事务表中的提交标志)。Lck=1表示锁定了1行数据。
uba: 0x00800261.01a1.27 由三部分组成:
1)0x00800261是真正的uba地址;
2)01a1是XIDSQN;
3)27表示是undo记录中的第0x27条记录;
SQL> select xid,xidusn,xidslot,xidsqn,ubablk,ubafil from v$transaction;
XID XIDUSN XIDSLOT XIDSQN UBABLK UBAFIL
---------------- ---------- ---------- ---------- ---------- ----------
04001500E7010000 4 21 487 609 2
我们可以验证:
SQL> select to_number('00800261','xxxxxxxx') from dual;
TO_NUMBER('00800261','XXXXXXXX')
--------------------------------
8389217
SQL> select dbms_utility.data_block_address_file(8389217) from dual;
DBMS_UTILITY.DATA_BLOCK_ADDRESS_FILE(8389217)
---------------------------------------------
2
SQL> select dbms_utility.data_block_address_block(8389217) from dual;
DBMS_UTILITY.DATA_BLOCK_ADDRESS_BLOCK(8389217)
----------------------------------------------
609
然后执行 alter system dump datafile 2 block 609; 就可以将undo块dump出来:
Start dump data blocks tsn: 1 file#: 2 minblk 609 maxblk 609
buffer tsn: 1 rdba: 0x00800261 (2/609)
scn: 0x0000.00103364 seq: 0x01 flg: 0x04 tail: 0x33640201
frmt: 0x02 chkval: 0x276e type: 0x02=KTU UNDO BLOCK
Hex dump of block: st=0, typ_found=1
Dump of memory from 0x0CC2B400 to 0x0CC2D400
... ...
********************************************************************************
UNDO BLK:
xid: 0x000a.025.000001e6 seq: 0x1d4 cnt: 0x33 irb: 0x33 icl: 0x0 flg: 0x0000
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
---------------------------------------------------------------------------
0x01 0x1f48 0x02 0x1e90 0x03 0x1df0 0x04 0x1d48 0x05 0x1cd4
0x06 0x1c44 0x07 0x1b9c 0x08 0x1b04 0x09 0x1a6c 0x0a 0x19e8
0x0b 0x195c 0x0c 0x18d8 0x0d 0x185c 0x0e 0x1788 0x0f 0x1704
0x10 0x1664 0x11 0x15ac 0x12 0x1510 0x13 0x142c 0x14 0x13c4
0x15 0x136c 0x16 0x12e8 0x17 0x1280 0x18 0x11bc 0x19 0x10c8
0x1a 0x1030 0x1b 0x0f94 0x1c 0x0e9c 0x1d 0x0e04 0x1e 0x0d4c
0x1f 0x0cc4 0x20 0x0bfc 0x21 0x0b74 0x22 0x0aac 0x23 0x0a24
0x24 0x0978 0x25 0x0904 0x26 0x0858 0x27 0x07dc 0x28 0x0730
0x29 0x06b4 0x2a 0x0608 0x2b 0x058c 0x2c 0x04e0 0x2d 0x0464
0x2e 0x03b8 0x2f 0x033c 0x30 0x0290 0x31 0x0214 0x32 0x0168
0x33 0x00ec
... ...
*-----------------------------
* Rec #0x27 slt: 0x15 objn: 53159(0x0000cfa7) objd: 53159 tblspc: 0(0x00000000)
* 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: 0x00800261.01a1.26 ctl max scn: 0x0000.00102c89 prv tx scn: 0x0000.00102cdb
txn start scn: scn: 0x0000.00103234 logon user: 0
prev brb: 8389214 prev bcl: 0
KDO undo record:
KTB Redo
op: 0x04 ver: 0x01
op: L itl: xid: 0x000a.017.000001e6 uba: 0x008002b8.01d4.12
flg: C--- lkc: 0 scn: 0x0000.00102ee8
KDO Op code: URP row dependencies Disabled
xtype: XA flags: 0x00000000 bdba: 0x0040fb72 hdba: 0x0040fb71
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 1(0x1) flag: 0x2c lock: 0 ckix: 0
ncol: 2 nnew: 1 size: -2
col 1: [ 1] 62
End dump data blocks tsn: 1 file#: 2 minblk 609 maxblk 609
分析如下:
Rec #0x12 表示第0x12条undo记录;
slt: 0
x15 表示slot=0x15=21 与上面从v$transaction中查出来的一致;
col 1: [ 1] 62 表示具体的undo数据,即update之前的数据'b';
我们感性地看到了undo的记录条目,以及如何保持修改之前的数据。我们从数据块,
2. undo段的组成:
undo段是自动管理的,可以分为:回滚段段头块、回滚块。
段头块中有一个十分重要的概念:事务表,事务表用于保存使用该undo段的事务的信息,比如xid, uba等。事务表有47行,所以每一个回滚段最多可以47个事务来使用。
当一个事务开始时,会找到一个undo段,然后在段头块中的事务表中,找到一行填上xid, uba等信息。然后在该undo段中分配undo块(回滚块),用来保存被该事务修改之前的数据。
同时,会在ITL事务槽中也保存xid, uba等事务信息。最后才是修改数据。所以事务中的一条DML语句会涉及到四处修改地方(事务表、事务槽、回滚块、数据块),注意所有这些修改都会产生redo log信息。
三个概念:事务表、回滚块、事务槽
事务表、事务槽(存放事务信息:xid 事务ID, uba 回滚块的地址);
回滚块(存放回滚数据)。
3. xid事务ID的组成:
xid既是一个事务ID,它其中包含了三部分的信息:
1)回滚段的段头块编号,
2)事务表中47行中的哪一行,
3)以及是第几次被使用/覆盖
事务表、回滚块、事务槽 三者之间的关系如下:
3. ORA-01555错误
ORA_10555 "Snapshot too old" 是Oracle的一个经典错误。
原因:1)select语句执行时间太长;2)undo表空间太小,导致undo数据被覆盖;
解决办法:增加undo表空间大小,可以使用em中的undo advisor(指导中心-->>还原管理)
ORA_10555 "Snapshot too old"
ORA-10555的两个案例: