事务,undo,cleanout,1555

最近在网上看了一些资料 研究了下数据块内部的东西 对网上一些高人发表的东西做了些整理和加工 和大家分享一下
可能白话用词比较多,显得比较罗嗦,主要是因为这部分东西我也是刚接触,还在学习中,所以我觉得写得还不算透彻,
有什么不对的地方希望大家指正,下面的内容还可以继续往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的控制..
后来利用打开游标的特点才完成了这个实验.













标签: 暂无标签
salo120

写了 1 篇文章,拥有财富 52,被 2 人关注

转播转播 分享分享 分享淘帖
回复

使用道具

P6 | 发表于 2012-11-5 16:54:23
支持一下
回复

使用道具

您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

意见
反馈