最近在网上看了一些资料 研究了下数据块内部的东西 对网上一些高人发表的东西做了些整理和加工 和大家分享一下
可能白话用词比较多,显得比较罗嗦,主要是因为这部分东西我也是刚接触,还在学习中,所以我觉得写得还不算透彻,
有什么不对的地方希望大家指正,下面的内容还可以继续往redo方面扩展,有时间整理了redo的东西会再补充
首先通过一些操作让我们得到一个针对datafile block的转储文件
SQL> select * from v$version;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.5.0 - Prod
PL/SQL Release 10.2.0.5.0 - Production
CORE 10.2.0.5.0 Production
TNS for Linux: Version 10.2.0.5.0 - Production
NLSRTL Version 10.2.0.5.0 - Production
这是以下所有操作所在的数据库版本
session 1:
SQL> show user
USER is "SCOTT"
SQL> create table testx (id number,name varchar2(10));
Table created.
SQL> insert into testx values(1,'aaa');
1 row created.
SQL> insert into testx values(2,'bbb');
1 row created.
SQL> select xidusn,xidslot,xidsqn,ubafil,ubablk,ubasqn,ubarec from v$transaction;
XIDUSN XIDSLOT XIDSQN UBAFIL UBABLK UBASQN UBAREC
---------- ---------- ---------- ---------- ---------- ---------- ----------
3 13 415 2 345 367 43
这时从v$transaction中我们可以看到一个事务开始后分配给这个事务的
xidusn(回滚段号)
xidslot(itl列表中的slot#)
xidsqn(当前slot被重复使用的次数),
这三列的组合就是所谓的transaction id
后面的几列是uba(Undo Block Address)的相关信息
ubafil(回滚段文件号)
ubablk(数据块号)
ubasqn(回滚序列号)
ubarec(回滚记录号)
dbms_rowid这个包内的函数可以将rowid作为参数并返回数据文件号及块号
SQL> select dbms_rowid.rowid_relative_fno(rowid) datafile#,dbms_rowid.rowid_block_number(rowid) block# from testx;
DATAFILE# BLOCK#
---------- ----------
4 68
4 68
这就得到了我们接下来会进行转储的数据块,此时之前的insert操作并未进行提交,现在另开一个session
session 2:
SQL> alter system dump datafile 4 block 68;
System altered.
生成的trc文件会在udump下,下面是trc文件中的部分内容,也是比较关注的内容
###########transaction header############
Block header dump: 0x01000044
Object id on Block? Y
seg/obj: 0xd7fd csc: 0x00.1c4095f itc: 2 flg: E typ: 1 - DATA
brn: 0 bDBA: 0x1000041 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0003.00d.0000019f 0x00800159.016f.2b ---- 2 fsc 0x0000.00000000
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
...
...
...
########### data area ############
block_row_dump:
tab 0, row 0, @0x1f8e
tl: 10 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] 61 61 61
tab 0, row 1, @0x1f84
tl: 10 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] 62 62 62
end_of_block_dump
这是在转储文件中看到的块头部信息及块中所包含的数据行的信息
seg/obj: 0xd7fd -- 在obj$中记录的segment的object number
csc: 0x00.1c4095f -- 最后一次该块clean out的scn
itc: 2 -- itl的数量
typ: 1 -- 存储数据的类型,1:data 2:index
xid:以.分隔,对应了XIDUSN,XIDSLOT,XIDSQN
Uba:以.分割,对应了UBAFIL,UBABLK,UBASQN,UBAREC
flag:
---- = transaction is active or committed pending cleanout
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
C-U- = 块延迟清楚后的状态,也就是用一个upper bound scn作为提交时刻的scn
Lck:这就是所谓的行级锁(所影响的行数)
scn/fsc:如果此事务被cleanout,则改值为scn,否则为事务结束后所能释放的字节数
在data area中
lb:表示当前锁的itl编号,通过上面的0x1就可以和Itl的0x01槽对应上
col n:每个行上的各列值
fb:该行的一个标识符
H Head of row piece
K Cluster key
D Deleted row
F First data piece
L Last data piece
P First column continues from previous location
通过进制转换可以更好的解读一下这些内容
seg/obj: 0xd7fd 对应到obj#
SQL> select to_number('d7ef','xxxx') from dual;
TO_NUMBER('D7EF','XXXX')
------------------------
55279
SQL> select obj#,owner#,name from obj$ where obj#=55279;
OBJ# OWNER# NAME
---------- ---------- ------------------------------
55279 57 TESTX
Xid:0x0003.00d.0000019f 对应到v$transaction中的xidusn,xidslot,xidsqn
SQL> select to_number('0003','xxxx'),to_number('00d','xxx'),to_number('0000019f','xxxxxxxx') from dual;
TO_NUMBER('0003','XXXX') TO_NUMBER('00D','XXX') TO_NUMBER('0000019F','XXXXXXXX
------------------------ ---------------------- ------------------------------
3 13 415
Uba:0x00800159.016f.2b 对应到v$transaction中的ubafil,ubablk,ubasqn,ubarec
但是这里的计算稍微有些不同
第一段的800159需要先转换成2进制,前十位组合在一起是文件号,后面的是块号(二进制转换可以使用ora自带的bin_to_num函数),后两段直接由
16进制转换成10进制即可
貌似ora没有自带的函数能将16进制变为2进制,不过网上能搜到自建的function代码,这里就不说代码的内容了
总之通过转换可以得到的内容为ubafil=2 ubablk=345 uba=367 ubarec=43 也是和v$transaction中的内容相对应的
在data area中挑取一行,从这行来获取一下在表中我们能看到的数据值
tab 0, row 1, @0x1f84
tl: 10 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] 62 62 62
SQL> select * from testx;
ID NAME
---------- ----------
1 aaa
2 bbb
col 1所对应的name列(字符)比较简单,实际上就是十六进制到十进制再进行一次asc码的转换就能还原列值
SQL> select chr(to_number('62','xx')) from dual;
CHR(TO_NUMBER('62','XX'))
-------------------------
b
col 0所对应的id
这里又要麻烦一些了....
先说一下
col后面的值是从何而来
实际上就是dump数据块的时候对相应的数据内容使用了dump()这个函数
SQL> select dump(id,16) dmpid,id,dump(name,16) dmpnm,name from testx;
DMPID ID DMPNM NAME
-------------------- ---------- ------------------------- ----------
Typ=2 Len=2: c1,2 1 Typ=1 Len=3: 61,61,61 aaa
Typ=2 Len=2: c1,3 2 Typ=1 Len=3: 62,62,62 bbb
DUMP函数的输出格式类似:
类型 <[长度]>,符号/指数位 [数字1,数字2,数字3,......,数字20]
typ就是类型:2为数字类的,1为字符类的 其他的 code在官方文档datatypes中都可以查到
len为存储的字节数
c1是符号/指数位(我理解为换算的一个系数)
在存储上,Oracle对正数和负数分别进行存储转换:
正数:加1存储(为了避免Null)
负数:被101减,如果总长度小于21个字节,最后加一个102(是为了排序的需要)
指数位换算:
正数:指数=符号/指数位 - 193 (最高位为1是代表正数)
负数:指数=62 - 第一字节
######################################
一个比较完整的例子
SQL> select dump(123456.789,16) from dual;
DUMP(123456.789,16)
-----------------------------
Typ=2 Len=6: c3,d,23,39,4f,5b
数字123456.789经过转储后在文件中会以上面所示的16进制形式展现,为方便计算,我们在下面直接获得未进行进制转换的转储值
SQL> select dump(123456.789) from dual;
DUMP(123456.789)
-------------------------------
Typ=2 Len=6: 195,13,35,57,79,91
<指数>: 195 - 193 = 2
<数字1> 13 - 1 = 12 *100^(2-0) 120000
<数字2> 35 - 1 = 34 *100^(2-1) 3400
<数字3> 57 - 1 = 56 *100^(2-2) 56
<数字4> 79 - 1 = 78 *100^(2-3) .78
<数字5> 91 - 1 = 90 *100^(2-4) .009
123456.789
这就是一个还原的过程
######################################
再看我们当前的值
c1通过进制转换后为10进制的193
col0
<指数>: 193 - 193 = 0
<数字1> 2 - 1 = 2 *100^(0-0) = 1
col1
<指数>: 193 - 193 = 0
<数字1> 3 - 1 = 2 *100^(0-0) = 2
通过上面的内容,已经可以对转储后的块文件进行一个大致的阅读了,介绍的不是太完全,因为有些地方我还在摸索...
现在已经知道XIDUSN的值了,下面我们可以来看看undo内有些什么
将刚才的两条insert提交后进行一次update
SQL> select * from testx;
ID NAME
---------- ----------
1 aaa
2 bbb
SQL> update testx set name='abc' where id=2;
1 row updated.
再另一个session再次对该块进行dump(规避一下alter的隐式提交)
SQL> alter system dump datafile 4 block 68;
System altered.
这时trc文件中的内容出现了下面的变化
Block header dump: 0x01000044
Object id on Block? Y
seg/obj: 0xd7fd csc: 0x00.1c40bd5 itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x1000041 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0003.00d.0000019f 0x00800159.016f.2b C--- 0 scn 0x0000.01c40a34
0x02 0x000a.02b.0000019b 0x008004a9.01c2.05 ---- 1 fsc 0x0000.00000000
这部分内容首先可以看到cleanout scn的改变,缘于对刚才两次insert的提交
itl第一个槽的flag已经变成了C---意味着刚才的事务被提交并且清除了lock,同时有了提交时的scn
itl第二个槽显示了当前的活动事务,也就是刚刚做出的update动作
再看后面的block row dump
block_row_dump:
tab 0, row 0, @0x1f8e
tl: 10 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] 61 61 61
tab 0, row 1, @0x1f84
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] 61 62 63
end_of_block_dump
row 1中的col 1已经由刚才的 61 61 61 变成了 61 62 63 也就是新值abc
现在来看看undo中内容
SQL> select xidusn,xidslot,xidsqn,ubafil,ubablk,ubasqn,ubarec from v$transaction;
XIDUSN XIDSLOT XIDSQN UBAFIL UBABLK UBASQN UBAREC
---------- ---------- ---------- ---------- ---------- ---------- ----------
10 43 411 2 1193 450 5
SQL> select * from v$rollname;
USN NAME
---------- ------------------------------
0 SYSTEM
1 _SYSSMU1$
2 _SYSSMU2$
3 _SYSSMU3$
4 _SYSSMU4$
5 _SYSSMU5$
6 _SYSSMU6$
7 _SYSSMU7$
8 _SYSSMU8$
9 _SYSSMU9$
10 _SYSSMU10$
11 rows selected
SQL> alter system dump undo header '_SYSSMU10$';
System altered.
对所分配的10号回滚段段头进行了转储,转储文件最下面的TRN TBL就是常说的事务表
TRN TBL::
index state cflags wrap# uel scn dba parent-xid nub stmt_num cmt
------------------------------------------------------------------------------------------------
0x00 9 0x00 0x019c 0x002c 0x0000.01c40ae7 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304009441
0x01 9 0x00 0x019c 0x0002 0x0000.01c4095e 0x00000000 0x0000.000.00000000 0x00000000 0x00000000 1304008425
0x02 9 0x00 0x019c 0x001e 0x0000.01c40abf 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304009441
0x03 9 0x00 0x019b 0x001b 0x0000.01c402c3 0x008004a0 0x0000.000.00000000 0x00000003 0x00000000 1304004640
0x04 9 0x00 0x019a 0x0026 0x0000.01c408bf 0x008004a6 0x0000.000.00000000 0x00000003 0x00000000 1304008241
0x05 9 0x00 0x019c 0x0004 0x0000.01c408be 0x008004a5 0x0000.000.00000000 0x00000001 0x00000000 1304008241........
......
......
0x23 9 0x00 0x019b 0x0005 0x0000.01c408ae 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304008241
0x24 9 0x00 0x019b 0x0027 0x0000.01c408e0 0x008004a7 0x0000.000.00000000 0x00000002 0x00000000 1304008242
0x25 9 0x00 0x019b 0x0000 0x0000.01c40add 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304009441
0x26 9 0x00 0x019a 0x0009 0x0000.01c408c0 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304008241
0x27 9 0x00 0x019b 0x0001 0x0000.01c4090b 0x00000000 0x0000.000.00000000 0x00000000 0x00000000 1304008287
0x28 9 0x00 0x019b 0x0007 0x0000.01c40b0f 0x008004a9 0x0000.000.00000000 0x00000001 0x00000000 1304009441
0x29 9 0x00 0x019a 0x0008 0x0000.01c40106 0x0080049d 0x0000.000.00000000 0x00000001 0x00000000 1304003439
0x2a 9 0x00 0x019b 0x0006 0x0000.01c406d8 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304007041
0x2b 10 0x80 0x019b 0x0010 0x0000.01c40bd5 0x008004a9 0x0000.000.00000000 0x00000001 0x00000000 0
0x2c 9 0x00 0x019b 0x0010 0x0000.01c40af1 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304009441
0x2d 9 0x00 0x019a 0x000e 0x0000.01c405be 0x008004a0 0x0000.000.00000000 0x00000001 0x00000000 1304006441
0x2e 9 0x00 0x019b 0x0025 0x0000.01c40ad3 0x008004a3 0x0000.000.00000000 0x00000001 0x00000000 1304009441
state中数值是10这行代表着活动的事务,其他的均为不活动的,这行中的dba列0x008004a9就是在数据块文件转储中Uba的第一段内容,
可以将其转换为undo的datafile#及block#
当然也可以通过v$transaction查到这两个值
SQL> select xidusn,xidslot,xidsqn,ubafil,ubablk,ubasqn,ubarec from v$transaction;
XIDUSN XIDSLOT XIDSQN UBAFIL UBABLK UBASQN UBAREC
---------- ---------- ---------- ---------- ---------- ---------- ----------
10 43 411 2 1193 450 5
SQL> alter system dump datafile 2 block 1193;
System altered.
对改undo数据块进行转储
UNDO BLK:
xid: 0x000a.02b.0000019b seq: 0x1c2 cnt: 0x5 irb: 0x5 icl: 0x0 flg: 0x0000
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
---------------------------------------------------------------------------
0x01 0x1f70 0x02 0x1f1c 0x03 0x1e74 0x04 0x1dac 0x05 0x1d10
v$transaction中的ubarec转换成16进制后就是undo blk中对应的Rec值,通过这个值或者是uba我们都可以在转储文件中找到下面所看到的具体的内容
*-----------------------------
* Rec #0x5 slt: 0x2b objn: 55293(0x0000d7fd) objd: 55293 tblspc: 4(0x00000004)
* 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: 0x008004a9.01c2.04 ctl max scn: 0x0000.01c400dc prv tx scn: 0x0000.01c400e8
txn start scn: scn: 0x0000.01c40bd5 logon user: 57
prev brb: 8389789 prev bcl: 0
KDO undo record:
KTB Redo
op: 0x03 ver: 0x01
op: Z
Array Update of 1 rows:
tabn: 0 slot: 1(0x1) flag: 0x2c lock: 0 ckix: 0
ncol: 2 nnew: 1 size: 0
KDO Op code: 21 row dependencies Disabled
xtype: XAxtype KDO_KDOM2 flags: 0x00000080 bdba: 0x01000044 hdba: 0x01000043
itli: 2 ispac: 0 maxfr: 4858
vect = 3
col 1: [ 3] 62 62 62
这里的col 1: [ 3] 62 62 62就是update操作之前该行第二列name的旧值
通过以上的部分,大致可以了解到了一个事务操作的一些内在变化,其实看懂trace也不是非常的难,
这里额外说下就是关于一些误操作,例如删除了数据并提交,其实在数据块内部被删除的数据还在,
只不过在flag上面添加了D这个标识,如果在没有备份的情况下进行恢复看来也不是不可能的事情,
前提就是改块没有被重用覆盖,相老师有没有关于非常规数据恢复的文档?对这块比较感兴趣
借着上面的东西,下面来说一说块延迟清除,因为这个现象有可能发生1555这个错误(虽然更多的时候
1555是由于undo空间问题或者sql性能问题引起的)
块延迟清楚:一个大事务提交的时候已经被dbwr写到数据文件中去的块和那些超过buffer的10%的块不会被cleanout,
就是说commit的时候不会修改块头的itl事务槽,不会在上面标记scn,并且row lock标识也不会清除,
这个工作会由后来的事务比如select完成,这样做的目的还是为了用户体验,避免一次清理过多的block,
但如果如果一个查询在scan到这个block的时候,发现这个块需要cleanout,他就会根据itl的信息,去找
回滚段中记录的commit scn,如果找到了,那自然ok,cleanout完成,查询正常,如果不幸没有找到,那么
就会开始进行query scn和undo scn的比较,如果undo中存在比query scn小的scn,数据库就会以这个scn
为cleanout的scn,也就是猜了一个scn出来,但如果更不幸的是undo中的scn全部都比query scn大了,就会
报出经典的1555
session a:
建立一个较小的undo并且修改数据库的undo参数设置,然后建立一个5-10w行的表一会儿用来刷undo
SQL> show user
USER is "SYS"
SQL> create undo tablespace undotbs2 datafile '/tmp/undo_test.dbf' size 2m autoextend off;
Tablespace created.
SQL> alter system set undo_tablespace=undotbs2;
System altered.
SQL> create table scott.dba_obj as select dba_objects.*,mod(object_id,99) mod from dba_objects order by mod(object_id,99);
Table created.
SQL> create table scott.test as select * from scott.dba_obj;
Table created.
SQL> insert into scott.test select dba_objects.*,mod(object_id,99) mod from dba_objects order by mod(object_id,99);
51061 rows created.
SQL> commit;
Commit complete.
session b:
建立一个查询用的测试表,定义一个游标并打开备用,这里算是一个关键,因为这是仅仅是打开了游标,还并没有进行执行和fetch的动作,
可是此时在这个session的PGA中已经有开辟了一块内存来存储游标了,里面记录着打开时的scn
SQL> show user
USER is "SCOTT"
SQL> create table test_1555 (id number);
Table created.
SQL> insert into test_1555 values(1);
1 row created.
SQL> insert into test_1555 values(2);
1 row created.
SQL> commit;
Commit complete.
SQL> var t1555 refcursor
SQL> begin
2 open :t1555 for select * from test_1555;
3 end;
4 /
PL/SQL procedure successfully completed.
session c:
对测试表进行一个update,但是不要commit
SQL> show user
USER is "SCOTT"
SQL> update test_1555 set id=id+1;
2 rows updated.
session b:
回到第二个session,将buffer cache中的数据刷到datafile中
SQL> alter system flush buffer_cache;
System altered.
session c:
这时将刚才的update提交,块延迟清除的状态在此时就已经做成了
session a:
SQL> begin
2 for i in 1..40 loop
3 for j in 1..99 loop
4 update scott.test set mod=mod where mod=j;
5 commit;
6 end loop;
7 end loop;
8 end;
9 /
PL/SQL procedure successfully completed.
当undo被刷过之后,所有块的scn都会比刚才打开游标那一时刻的scn要大了
SQL> print :test;
SP2-0552: Bind variable "TEST" not declared.
SQL> print :t1555;
ERROR:
ORA-01555: snapshot too old: rollback segment number 29 with name "_SYSSMU29$"
too small
no rows selected
经典的1555报出来了,之前这个实验做了几次都失败了...失败的原因就是对select这个动作的scn的控制..
后来利用打开游标的特点才完成了这个实验.
|
|