Skip to content

MDATA

经过2016,修复了一些bug.

现在从新开放下载.企业版新增table drop后的lob恢复.

 

使用须知:

 

1.新版本下载后,建议使用jdk 1.4或者jdk 1.5运行.

2.新版不限制数据恢复量,不限制恢复行数,依然免费使用

3.新版程序绑定机器,需发邮件给我们提供运行时候的feature code,我们将根据feature code 生成注册码。

4.使用前请阅读下载文件中的使用说明书

 

联系邮箱:

 

feigigi@qq.com;

564439763@qq.com

 

下载地址:

 

Mdata person  5.0.1 Download

Oracle 恢复工具 Mdata 5.0.1 版本发布

unused column 和 drop column操作实质

unused column和drop column的操作从本质上讲是不一样的,unused是通过变更数据字典的信息让sql无法访问到column,而drop是直接在物理数据层做了变动。这里的操作后台跟踪可以用event 10046去验证,这里不做描述.

下面通过实验的方式验证unused和drop column的操作对比情况:

1.创建测试表

SQL> grant dba to luda;

Grant succeeded.

SQL> conn luda/luda
Connected.


SQL> create table luda_t1 as  select * from dba_objects;

Table created.

SQL> set timing on
SQL> set serverout on
SQL> exec showspace('LUDA_T1','LUDA')
Total Blocks............................768
Total Bytes.............................6291456
Unused Blocks...........................53
Unused Bytes............................434176
Last Used Ext FileId....................4
Last Used Ext BlockId...................2953
Last Used Block.........................75
*************************************************
The segment is analyzed
0% -- 25% free space blocks.............0
0% -- 25% free space bytes..............0
25% -- 50% free space blocks............0
25% -- 50% free space bytes.............0
50% -- 75% free space blocks............0
50% -- 75% free space bytes.............0
75% -- 100% free space blocks...........0
75% -- 100% free space bytes............0
Unused Blocks...........................0
Unused Bytes............................0
Total Blocks............................695
Total bytes.............................5693440

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL> select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
redo size                                                             80916

Elapsed: 00:00:00.01
SQL> select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='undo change vector size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
undo change vector size                                               21012

Elapsed: 00:00:00.00

2.设置object_name为unused

SQL> alter table luda_t1 set unused column object_name;

Table altered.

Elapsed: 00:00:00.02
SQL> exec showspace('LUDA_T1','LUDA')  ---- 对比操作前,可以发现luda_t1表存储信息未有变动
Total Blocks............................768
Total Bytes.............................6291456
Unused Blocks...........................53
Unused Bytes............................434176
Last Used Ext FileId....................4
Last Used Ext BlockId...................2953
Last Used Block.........................75
*************************************************
The segment is analyzed
0% -- 25% free space blocks.............0
0% -- 25% free space bytes..............0
25% -- 50% free space blocks............0
25% -- 50% free space bytes.............0
50% -- 75% free space blocks............0
50% -- 75% free space bytes.............0
75% -- 100% free space blocks...........0
75% -- 100% free space bytes............0
Unused Blocks...........................0
Unused Bytes............................0
Total Blocks............................695
Total bytes.............................5693440

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
SQL> select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
redo size                                                             92176

Elapsed: 00:00:00.00

---产生了少量redo日志

SQL> select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='undo change vector size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
undo change vector size                                               25212

Elapsed: 00:00:00.00

--产生了少量undo

3.执行drop unused column

SQL> alter table luda_t1 drop unused column;

Table altered.

Elapsed: 00:00:00.26
SQL> exec showspace('LUDA_T1','LUDA') --对比操作前可以发现在freespace层面25%-50%多出了642的block
Total Blocks............................768
Total Bytes.............................6291456
Unused Blocks...........................53
Unused Bytes............................434176
Last Used Ext FileId....................4
Last Used Ext BlockId...................2953
Last Used Block.........................75
*************************************************
The segment is analyzed
0% -- 25% free space blocks.............0
0% -- 25% free space bytes..............0
25% -- 50% free space blocks............642
25% -- 50% free space bytes.............5259264
50% -- 75% free space blocks............0
50% -- 75% free space bytes.............0
75% -- 100% free space blocks...........0
75% -- 100% free space bytes............0
Unused Blocks...........................0
Unused Bytes............................0
Total Blocks............................53  --总占用block降低为53个,总块数不变642+53=695
Total bytes.............................434176

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL>  select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
redo size                                                          12393932

--产生大量的redo日志相对比上一次操作的redo量
Elapsed: 00:00:00.01
SQL> select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='undo change vector size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
undo change vector size                                             5128064
--产生大量的undo信息相对比上一次操作的undo量
Elapsed: 00:00:00.00
SQL>

4.执行drop column的测试

SQL> alter table luda_t1 drop column object_type;

Table altered.

Elapsed: 00:00:00.25
SQL> exec showspace('LUDA_T1','LUDA') -- drop 操作效果与drop unused一致,释放空间,降低高水位
Total Blocks............................768
Total Bytes.............................6291456
Unused Blocks...........................53
Unused Bytes............................434176
Last Used Ext FileId....................4
Last Used Ext BlockId...................2953
Last Used Block.........................75
*************************************************
The segment is analyzed
0% -- 25% free space blocks.............0
0% -- 25% free space bytes..............0
25% -- 50% free space blocks............664
25% -- 50% free space bytes.............5439488
50% -- 75% free space blocks............1
50% -- 75% free space bytes.............8192
75% -- 100% free space blocks...........0
75% -- 100% free space bytes............0
Unused Blocks...........................0
Unused Bytes............................0
Total Blocks............................30
Total bytes.............................245760

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
SQL>
SQL> select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
redo size                                                          23902388
--产生大量的redo日志相对比上一次操作的redo量
Elapsed: 00:00:00.01
SQL> select b.name,a.value from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name='undo change vector size';
--产生大量的undo信息相对比上一次操作的undo量
NAME                                                                  VALUE
---------------------------------------------------------------- ----------
undo change vector size                                             9439452

Elapsed: 00:00:00.00
SQL>

这里验几个情况:

1.unused column只产生少量的redo和undo,真实在表存储部分并未做变动,高水位线没有变动.真实数据部分并未被oracle处理,而根据trace信息可以发现unused column是在数据字典层面做的变动,对被unused操作的字段打上对于的flag.

2.drop unused column 操作会对被标记为unused flag的数据进行rewrite(trace可以发现)并释放空间,降低高水位,同时产生大量的redo和undo.

3.drop column操作会对整个字段物理数据部分直接进行删除(bbed可以发现),并更新table entries.同时降低高水位产生大量的redo和undo.相当于一次数据重组.

以下的set unused 以及drop column的trace结果图作为参考:

unused_column

drop_column

Mdata 4.0.3 更新下载 新增asm unload table

版本更新到4之后,后面的版本一直没有放开下载,因为要修复验证bug.经过一段时间的测试,修复了部分已经发现的bug,比如system多数据文件情况下识别错误,dmp格式导出后无法导入的问题.

放在这个论坛上下载是为了把Mdata的问题提交到该站上面,这样Mdata维护以及更新起来较为方便.Mdata一直都是免费使用,是程序难免有bug,我们从Mdata 1.0版本到现在更新了如此多的版本就是不断的修复各种bug.我和hc都是利用工作之外的时间来处理,因此还请谅解!

大家以后碰到mdata的使用问题或者报错请发截图以及日志到Mdata使用问题版块上,我们会跟进处理以及更新.

目前最新版本为Mdata 4.0.3 ,新增在Oracle asm instance下直接unload table的功能.
下载选择附件.以后每2个月周期都会发步新的下载版本.

下载地址:
http://www.dataunload.net/?/article/20

Mdata针对column删除恢复的支持情况说明

从前面分析的unused column和drop column的实质结果上可以获得初步的结论,在使用drop column后是无法使用恢复工具恢复这部分drop的数据的,dul也好mdata也罢都回天无力,只能借助有备份的情况下基于时间点的恢复或者oracle12c中的表恢复功能。

而由于unused的特性,使得unused column部分的时间是可以通过mdata恢复的,需要使用到drop table的恢复机制,通过指定恢复表的字段属性列表,还有objdataid来恢复对应的数据。这部分可以用现用的mdata 3.62版本测试。这里不作描述。

测试案例:

1

从行构成的微观角度解析Delete操作的实质

无外乎delete的操作最终的变更导向也是指向底层数据块部分,所以在这部分需要了解数据块的基本构成以及行peice的结构.

先看以下的data block structure explain.

KTB Data Block (Table/Cluster)
 struct kcbh, 20 bytes                      @0       --Cache Layer
    ub1 type_kcbh                           @0                 -- Block type (see Header Block Types below)
    ub1 frmt_kcbh                           @1                 --Block format 1=Oracle 7, 2=Oracle 8+
    ub1 spare1_kcbh                         @2                 --Not used
    ub1 spare2_kcbh                         @3                 --Not used
    ub4 rdba_kcbh                           @4                 --RDBA -Relative Data Block Address
    ub4 bas_kcbh                            @8                 --SCN Base
    ub2 wrp_kcbh                            @12                --SCN Wrap
    ub1 seq_kcbh                            @14                --Sequence number, incremented for every change made to the block at the same SCN
    ub1 flg_kcbh                            @15                --Flag:
        0x01 New Block
        0x02 Delayed Logging Change advanced SCN/seq
               0x04 Check value saved - block XOR‘s to zero
        0x08 Temporary block

    ub2 chkval_kcbh                         @16               --Optional block checksum (if DB_BLOCK_CHECKSUM=TRUE)
    ub2 spare3_kcbh                         @18               --Not used
 struct ktbbh, 72 bytes                     @20       --Transaction Layer (Transaction Fixed Header Structure)
    ub1 ktbbhtyp                            @20               --Block type (1=DATA, 2=INDEX)
    union ktbbhsid, 4 bytes                 @24               --DATA OBJECT ID
    struct ktbbhcsc, 8 bytes                @28               --SCN at last block cleanout
    b2 ktbbhict                             @36               --Number of ITL slots
    ub1 ktbbhflg                            @38               --0=on the freelist
    ub1 ktbbhfsl                            @39               --ITL TX freelist slot
    ub4 ktbbhfnx                            @40               -- DBA of next block on the freelist
    struct ktbbhitl[2], 48 bytes            @44               --ITL list index
 struct kdbh, 14 bytes                      @100      --Data Header (Data Header Structure )
    ub1 kdbhflag                            @100          --N=pctfree hit(clusters); F=do not put on freelist; K=flushable cluster keys
    b1 kdbhntab                             @101              --Number of tables (>1 in clusters)
    b2 kdbhnrow                             @102              --Number of rows
    sb2 kdbhfrre                            @104              --First free row entry index; -1 = you have to add one
    sb2 kdbhfsbo                            @106              --Freespace begin offset
    sb2 kdbhfseo                            @108              --Freespace end offset
    b2 kdbhavsp                             @110              --Available space in the block
    b2 kdbhtosp                             @112              --Total available space when all TXs commit
 struct kdbt[1], 4 bytes                    @114    -- Table Directory
    b2 kdbtoffs                             @114
    b2 kdbtnrow                             @116

 sb2 kdbr[4]                                @118    --Row Directory
 ub1 freespace[7970]                        @126     -- Free Space
 ub1 rowdata[92]                            @8096    -- Row Data
 ub4 tailchk                                @8188    --Tailchk

一般删除数据指的对block type为05的data block上的row piece进行操作,在data block中与行有关的部分主要为row directory(kdbr)以及rowdata.
kdbr里面包含相关信息如下:

BBED> p kdbr      --p kdbr打印出row directory的信息。
sb2 kdbr[0]                                 @118      8062
sb2 kdbr[1]                                 @120      8040
sb2 kdbr[2]                                 @122      8020
sb2 kdbr[3]                                 @124      7996

BBED> p *kdbr[0]
rowdata[66]
-----------
ub1 rowdata[66]                             @8162     0x2c
BBED> p *kdbr[1]
rowdata[44]
-----------
ub1 rowdata[44]                             @8140     0x2c
BBED> p *kdbr[2]
rowdata[24]
-----------
ub1 rowdata[24]                             @8120     0x2c

上面显示,块中有4条记录,记录1开始于数据块偏移量8162,记录2开始于数据块偏移量8140,记录3开始于数据块偏移量8020。后面的0x2c 就是行头的FLAG值.

行头标记数据行头的一部分信息,在oracle程序kdr4.h部分记录着row header的定义,如下:

Row Overhead
The values for the row flag are:
#define KDRHFK 0x80 Cluster Key
#define KDRHFC 0x40 Clustered table member
#define KDRHFH 0x20 Head piece of row
#define KDRHFD 0x10 Deleted row
#define KDRHFF 0x08 First data piece|
#define KDRHFL 0x04 Last data piece
#define KDRHFP 0x02 First column continues from Previous piece
#define KDRHFN 0x01 Last column continues in Next piece

Row flag的算法有以下几种
1.普通表的行标记:

ROW_HEAD_PIECE + ROW_DELETED_ROW + ROW_FIRST_PIECE + ROW_LAST_PIECE= KDRHFH + KDRHFD + KDRHFF +KDRHFL

2.簇键的标记:

ROW_CLUSTER_KEY + ROW_HEAD_PIECE + ROW_FIRST_PIECE + ROW_LAST_PIECE + ROW_DELETED_ROW= KDRHFK KDRHFH + KDRHFD + KDRHFF +KDRHFL

3.簇表的行标记:

ROW_CTABLE_NUMBER + ROW_HEAD_PIECE + ROW_FIRST_PIECE + ROW_LAST_PIECE + ROW_DELETED_ROW= KDRHFK KDRHFH + KDRHFD + KDRHFF +KDRHFL

在这几种标记中,关键为row_deleted_row,让行被删除时候,KDRHFD变为0x10,未被删除则为0.

这里以普通表为例:
当行被删除时候,row flag为0x20+0x10+0x08+0x04=0x3c
当行未被删除时,row flag为0x20+0x08+0x04=0x2c

下面为验证测试案例,以Scott用户下的dept表为例.如果dept中的行记录被删除了,那么相应的行flag值将会变为0x3c ,如下:

SQL> select * from scott.dept;
    DEPTNO DNAME                        LOC
---------- ---------------------------- --------------------------
        10 ACCOUNTING                   NEW YORK
        20 RESEARCH                     DALLAS
        30 SALES                        CHICAGO
        40 OPERATIONS                   BOSTON
SQL> delete from scott.dept;    ---删除数据
4 rows deleted.
SQL> commit;
Commit complete.
SQL> alter system checkpoint;
System altered.
SQL> select * from scott.dept;  --验证删除
no rows selected
SQL>

登入bbed

BBED> set filename '/orcl/orcl/users01.dbf';
        FILENAME       /orcl/orcl/users01.dbf
BBED> set block 16  --通过rowid定位行所在块地址
        BLOCK#          16
BBED> print kdbr    --查看行目录,4行数据都还在
sb2 kdbr[0]                                 @118      7970
sb2 kdbr[1]                                 @120      8040
sb2 kdbr[2]                                 @122      8020
sb2 kdbr[3]                                 @124      7996
BBED> print *kdbr[0]
rowdata[0]
----------
ub1 rowdata[0]                              @8070     0x3c -- 行flag已被置为0x3c
BBED> print *kdbr[1]
rowdata[70]
-----------
ub1 rowdata[70]                             @8140     0x3c
BBED> print *kdbr[2]
rowdata[50]
-----------
ub1 rowdata[50]                             @8120     0x3c
BBED> print *kdbr[3]
rowdata[26]
-----------
ub1 rowdata[26]                             @8096     0x3c

可以看到原来的记录不光偏移量变了,标志位也变为了0x3c,也就是DELETE标志位被设置为了1。其实到了这里要知道如何恢复delete的数据已经非常明了,只需要针对row header部分的row_deleted_row做一些文章即可恢复出delete的数据,比如MDATA的批量恢复delete数据处理功能即是如此(需要首先加入object_id的定位表块).

Mdata恢复delete的恢复实践:
http://www.ludatou.com/?p=2016

这里把标志位0x3c改为0x2c,被删除的4行数据将会重新找回来。如下:

BBED> set offset 8070
        OFFSET          8070
BBED> dump /v count 16
 File: /orcl/orcl/users01.dbf (0)
 Block: 16      Offsets: 8070 to 8085  Dba:0x00000000
-------------------------------------------------------
 3c020302 c10b0a41 43434f55 4e54494e l <......ACCOUNTIN
 <16 bytes per line>
BBED> set mode edit   --设置编辑模式
        MODE            Edit
BBED> modify /x 2c    --更改row_deleted_row为非删除状态
 File: /orcl/orcl/users01.dbf (0)
 Block: 16               Offsets: 8070 to 8085           Dba:0x00000000
------------------------------------------------------------------------
 2c020302 c10b0a41 43434f55 4e54494e
 <32 bytes per line>
BBED> set offset 8140
        OFFSET          8140
BBED> dump /v count 16
 File: /orcl/orcl/users01.dbf (0)
 Block: 16      Offsets: 8140 to 8155  Dba:0x00000000
-------------------------------------------------------
 3c020302 c1150852 45534541 52434806 l <......RESEARCH.
 <16 bytes per line>
BBED> modify /x 2c
 File: /orcl/orcl/users01.dbf (0)
 Block: 16               Offsets: 8140 to 8155           Dba:0x00000000
------------------------------------------------------------------------
 2c020302 c1150852 45534541 52434806
 <32 bytes per line>
BBED> set offset 8120
        OFFSET          8120
BBED> dump /v count 16
 File: /orcl/orcl/users01.dbf (0)
 Block: 16      Offsets: 8120 to 8135  Dba:0x00000000
-------------------------------------------------------
 3c020302 c11f0553 414c4553 07434849 l <......SALES.CHI
 <16 bytes per line>
BBED> modify /x 2c
 File: /orcl/orcl/users01.dbf (0)
 Block: 16               Offsets: 8120 to 8135           Dba:0x00000000
------------------------------------------------------------------------
 2c020302 c11f0553 414c4553 07434849
 <32 bytes per line>
BBED> set offset 8096
        OFFSET          8096
BBED> dump /v count 16
 File: /orcl/orcl/users01.dbf (0)
 Block: 16      Offsets: 8096 to 8111  Dba:0x00000000
-------------------------------------------------------
 3c020302 c1290a4f 50455241 54494f4e l <....).OPERATION
 <16 bytes per line>
BBED> modify /x 2c
 File: /orcl/orcl/users01.dbf (0)
 Block: 16               Offsets: 8096 to 8111           Dba:0x00000000
------------------------------------------------------------------------
 2c020302 c1290a4f 50455241 54494f4e
 <32 bytes per line>
BBED> sum apply     --提交变更
Check value for File 0, Block 16:
current = 0x26ff, required = 0x26ff
BBED> verify   --验证块
DBVERIFY - Verification starting
FILE = /orcl/orcl/users01.dbf
BLOCK = 16
Block Checking: DBA = 16777232, Block Type = KTB-managed data block
data header at 0x110203064
kdbchk: the amount of space used is not equal to block size
        used=118 fsc=84 avsp=7970 dtl=8088
Block 16 failed with check code 6110
DBVERIFY - Verification complete
Total Blocks Examined         : 1
Total Blocks Processed (Data) : 1
Total Blocks Failing   (Data) : 1
Total Blocks Processed (Index): 0
Total Blocks Failing   (Index): 0
Total Blocks Empty            : 0
Total Blocks Marked Corrupt   : 0
Total Blocks Influx           : 0
BBED> print *kdbr[0]
rowdata[0]
----------
ub1 rowdata[0]                              @8070     0x2c
BBED> print *kdbr[1]
rowdata[70]
-----------
ub1 rowdata[70]                             @8140     0x2c
BBED> print *kdbr[2]
rowdata[50]
-----------
ub1 rowdata[50]                             @8120     0x2c
BBED> print *kdbr[3]
rowdata[26]
-----------
ub1 rowdata[26]                             @8096     0x2c
BBED> x /rncc offset 8070
rowdata[0]                                  @8070
----------
flag@8070: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8071: 0x02
cols@8072:    3
col    0[2] @8073: 10
col   1[10] @8076: ACCOUNTING
col    2[8] @8087: NEW YORK

BBED> x /rncc offset 8140
rowdata[70]                                 @8140
-----------
flag@8140: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8141: 0x02
cols@8142:    3
col    0[2] @8143: 20
col    1[8] @8146: RESEARCH
col    2[6] @8155: DALLAS

BBED> x /rncc offset 8120
rowdata[50]                                 @8120
-----------
flag@8120: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8121: 0x02
cols@8122:    3
col    0[2] @8123: 30
col    1[5] @8126: SALES
col    2[7] @8132: CHICAGO

BBED> x /rncc offset 8096
rowdata[26]                                 @8096
-----------
flag@8096: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8097: 0x02
cols@8098:    3
col    0[2] @8099: 40
col   1[10] @8102: OPERATIONS
col    2[6] @8113: BOSTON

再次回到sqlplus窗口,即可发现数据已经找回来:

SQL>  select * from scott.dept;
no rows selected
SQL> alter system flush buffer_cache;
System altered.
SQL> select * from scott.dept;
    DEPTNO DNAME                        LOC
---------- ---------------------------- --------------------------
        10 ACCOUNTING                   NEW YORK
        20 RESEARCH                     DALLAS
        30 SALES                        CHICAGO
        40 OPERATIONS                   BOSTON

到此,从底层的角度解释了delete的操作,希望对大家有帮助.

Oracle Truncate table原理剖析三:偷天换日也要找到你

如果说hellodba发明的truncate的手段恢复为移花接木,那么以下的手段可以称之为偷天换日,在此感谢伟翔同学的信息提供以及建议。

对于Truncate恢复,总结以下步骤:

1.遍历所有数据文件的数据块,寻找offset 1是23的(16进制),23代表段头块,同时还要和相应的Data Object Id相同的,这个需要检索offset 272的位置。(直接sqlplus就可以实现了)
2.找到了段头块,我们就可以通过offset 5192寻找到我们的L2块。
3.找到了L2块,我们就可以通过offset 116找到所有的L1块。找到了L1块,我们就等于找到了数据块。
4.至此,我们就可以开始反向的构造段头块。
5.修改段头块、L2块和第一个L1块的Data Object Id.,同时在修改数据字典。
6.修改段头块的高水位信息。当然这里的高水位块的辨别,一定会是在最后一个Extents上,你可以设置到最后一个Extnets的最后一个块,这个信息的准备性其实无所谓,全表扫描的时候它一定会扫描这个块下面所有的块。
7.修改段头上的Extents信息。

而在这些之前对segment header 结构的熟悉是必须的。

测试truncate恢复过程如下:

首先恢复段头,L1,L2等object_data_id为原object_data_id

BBED> set block 130
        BLOCK#          130

BBED> modify /x ca offset 272
Warning: contents of previous BIFILE will be lost. Proceed? (Y/N) y
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:  272 to  291           Dba:0x00000000
------------------------------------------------------------------------
 ca270100 00000010 80004001 08000000 00000000

BBED> set block 129
        BLOCK#          129

BBED> modify /x ca offset 104
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 129              Offsets:  104 to  123           Dba:0x00000000
------------------------------------------------------------------------
 ca270100 01000000 00000000 80004001 05000100

BBED> set block 128
        BLOCK#          128

BBED> modify /x ca offset 192
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 128              Offsets:  192 to  211           Dba:0x00000000
------------------------------------------------------------------------
 ca270100 e6151400 00000000 80004001 08000000

BBED> sum apply
Check value for File 0, Block 128:
current = 0x35a9, required = 0x35a9

还需要修改数据字典,否则会报错。

SQL> select * from a1.a;
select * from a1.a
                 *
ERROR at line 1:
ORA-08103: object no longer exists

SQL> update OBJ$ set DATAOBJ#= 75722 where OBJ#= 75722;
1 row updated.

SQL> commit;
Commit complete.

SQL> update TAB$ set DATAOBJ#=75722 where OBJ#=75722;
1 row updated.

SQL> commit;
Commit complete.

SQL> update  SEG$ set HWMINCR=75722 where FILE#=5 and BLOCK#=130; -- 这里的130为段头
1 row updated.

SQL> commit;
Commit complete.

SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.

SQL> startup
ORACLE instance started.

Total System Global Area  839282688 bytes
Fixed Size                  2233000 bytes
Variable Size             511708504 bytes
Database Buffers          322961408 bytes
Redo Buffers                2379776 bytes
Database mounted.
Database opened.

SQL> select * from a1.a;
no rows selected

2.段头高水位信息恢复

修改了这些东西后,我们会发现数据还是没有。我们还需要修改一些信息。一个很重要的信息就是段头上的高水位信息。在Truncate之前,段头上会记载。前面2节内容在解析L1块的时候说明过:
可以看到223后面直接就是225,直接此处跳空,这是因为我们的224是L1位图块,后面紧跟着我们刚刚说的225,226,227,228,229,230,231,232,233。但是问题是,这里看不到后面的234到239?这是因为234到239还是空闲没有格式化过的块,但是它已经被L1锁定了。
所以我们现在的高水位的块是234,一般做全表扫描的查询就会查高水位以下(234)的块。我们来看下我们现在的高水位。

BBED> dump /v offset 48 count 16
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:   48 to   63  Dba:0x00000000
-------------------------------------------------------
 00000000 03000000 08000000 83004001 l ..............@.

BBED> dump /v offset 92 count 16
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:   92 to  107  Dba:0x00000000
-------------------------------------------------------
 00000000 03000000 08000000 83004001 l ..............@.

SQL> select dbms_utility.data_block_address_file(to_number('01400083','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('01400083','xxxxxxxx')) as blockno from dual;

    FILENO    BLOCKNO
---------- ----------
         5        131

注意看这里的高水位是83004001,转换成文件号和块号,刚好是文件5块131。而前面的00000000,03000000,则代表着是扩展0,block 3,代表着高水位的位置。刚好是第一个extent的第三个块。128是L1,129是L2,130是段头块。而131则是第一个可以使用的数据块。所以这里记录了extent 0,block为3则代表了文件5的131号块。而08000000则代表了extent的大小,我们每个extents是由8个块组成的。
那在truncate之前,我们的高水位的块是文件5块234,我们从块128开始,每8个块是一个extent,234是第14个extent的第三个块。后面的6个块是没有插入数据的空块。这个在前面我dump 最后一个L1块得知。回顾一下,这里下面的块状态显示11111111 11000000。

BBED> dump /v offset 396 count 50
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 224     Offsets:  396 to  445  Dba:0x00000000
-------------------------------------------------------
 11111111 11000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 0000                                l ..

所以我们这个地方要把高水位从00000000 03000000 08000000 83004001修改成0d000000 02000000 08000000 ea004001。0d000000代表13,表明是第十四个扩展,02000000代表02,表明是第三个块开始,而08000000还是一样代表着这个扩展是8个块的大小,而ea004001则代表着文件5块234。

BBED> modify /x 0d offset 48
Warning: contents of previous BIFILE will be lost. Proceed? (Y/N) y
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:   48 to   63           Dba:0x00000000
------------------------------------------------------------------------
 0d000000 03000000 08000000 83004001

BBED> modify /x 02 offset 52
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:   52 to   67           Dba:0x00000000
------------------------------------------------------------------------
 02000000 08000000 83004001 00000000

BBED> modify /x ea offset 60
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:   60 to   75           Dba:0x00000000
------------------------------------------------------------------------
 ea004001 00000000 00000000 00000000

BBED> dump /v offset 48 count 16
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:   48 to   63  Dba:0x00000000
-------------------------------------------------------
 0d000000 02000000 08000000 ea004001 l ............
BBED> modify /x 0d offset 92
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:   92 to  107           Dba:0x00000000
------------------------------------------------------------------------
 0d000000 03000000 08000000 83004001

BBED> modify /x 02 offset 96
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:   96 to  111           Dba:0x00000000
------------------------------------------------------------------------
 02000000 08000000 83004001 00000000

BBED>  modify /x ea offset 104
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:  104 to  119           Dba:0x00000000
------------------------------------------------------------------------
 ea004001 00000000 00000000 00000000

BBED> dump /v offset 92 count 16
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:   92 to  107  Dba:0x00000000
-------------------------------------------------------
 0d000000 02000000 08000000 ea004001 l ............
BBED> sum apply
Check value for File 0, Block 130:
current = 0x8c54, required = 0x8c54

修改完成之后,刷新buffer cache,然后重新查询。

SQL> alter system flush buffer_cache;
System altered.
SQL> select count(1) from a1.a;
  COUNT(1)
----------
       142

3.Extents信息恢复
可以看到数据量不对,这是因为我们Truncate之后,在段头上只剩下了一个Extent的信息。而我们的Extents是有14个的,这需要我们在修改如下几个地方。Offset 264代表着我们Extents的数量,,这里修改成0e代表了14个extents。

BBED> modify /x 0e offset 264
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130              Offsets:  264 to  363           Dba:0x00000000
------------------------------------------------------------------------
 0e000000 00000000 ca270100 00000010 80004001 08000000 88004001 08000000
 90004001 08000000 98004001 08000000 a0004001 08000000 a8004001 08000000
 b0004001 08000000 b8004001 08000000 c0004001 08000000 c8004001 08000000
 d0004001

修改完Extents的数量之后,还需要添加对应的Extents Map的信息。因为我们的Extents Map信息也被删除了。从我们的offset 280开始。

BBED> dump /v offset 280 count 100
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:  280 to  379  Dba:0x00000000
-------------------------------------------------------
 80004001 08000000 00000000 00000000 l ..@.............
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000                            l ....

80004001 08000000这个Extents是我们第一个Extents。代表了文件5的块128。我们可以依次类推下列的信息出来。而08000000则代表有8个数据块构成一个Extents。

modify /x 8800 offset 288  modify /x 4001 offset 290  modify /x 08 offset 292
modify /x 9000 offset 296  modify /x 4001 offset 298  modify /x 08 offset 300
modify /x 9800 offset 304  modify /x 4001 offset 306  modify /x 08 offset 308
modify /x a000 offset 312  modify /x 4001 offset 314  modify /x 08 offset 316
modify /x a800 offset 320  modify /x 4001 offset 322  modify /x 08 offset 324
modify /x b000 offset 328  modify /x 4001 offset 330  modify /x 08 offset 332
modify /x b800 offset 336  modify /x 4001 offset 338  modify /x 08 offset 340
modify /x c000 offset 344  modify /x 4001 offset 346  modify /x 08 offset 348
modify /x c800 offset 352  modify /x 4001 offset 354  modify /x 08 offset 356
modify /x d000 offset 360  modify /x 4001 offset 362  modify /x 08 offset 364
modify /x d800 offset 368  modify /x 4001 offset 370  modify /x 08 offset 372
modify /x e000 offset 376  modify /x 4001 offset 378  modify /x 08 offset 380
modify /x e800 offset 384  modify /x 4001 offset 386  modify /x 08 offset 388

BBED> dump /v offset 280 count 128
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:  280 to  407  Dba:0x00000000
-------------------------------------------------------
 80004001 08000000 88004001 08000000 l ..@.......@.....
 90004001 08000000 98004001 08000000 l ..@.......@.....
 a0004001 08000000 a8004001 08000000 l ........
 b0004001 08000000 b8004001 08000000 l ........
 c0004001 08000000 c8004001 08000000 l ........
 d0004001 08000000 d8004001 08000000 l ........
 e0004001 08000000 e8004001 08000000 l ........
 00000000 00000000 00000000 00000000 l ................

 <16 bytes per line>

当然这些Extents Map修改完成之后,我们还需要在添加Auxillary Map。从Offset 2736开始。

BBED> dump /v offset 2736 count 200
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets: 2736 to 2935  Dba:0x00000000
-------------------------------------------------------
80004001 83004001 00000000 00000000 l ..@...@.........
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ...............

这里的80004001 83004001,代表这Extents中,L1块的地址和Data Block的地址,可以看到在Extents 1上面,它的L1块是128,而数据块是从131开始的。因为我们的129是L2,130是段头。所以下面我们构造其他的数据的时候,我们也要遵循这个规律。我们的Extents 2,它的L1还是128块,但是它的数据块却是从136开始的。而Extents 3,它的L1就是第二个L1块,也就是144,而它的数据块的开始则是从145开始的。依次类推下去。结果如下:

modify /x 8000 offset 2744  modify /x 4001 offset 2746
modify /x 8800 offset 2748  modify /x 4001 offset 2750
modify /x 9000 offset 2752  modify /x 9000 offset 2754
modify /x 9100 offset 2756  modify /x 4001 offset 2758
modify /x 9000 offset 2760  modify /x 4001 offset 2762
modify /x 9800 offset 2764  modify /x 4001 offset 2766
modify /x a000 offset 2768  modify /x 4001 offset 2770
modify /x a100 offset 2772  modify /x 4001 offset 2774
modify /x a000 offset 2776  modify /x 4001 offset 2778
modify /x a800 offset 2780  modify /x 4001 offset 2782
modify /x b000 offset 2784  modify /x 4001 offset 2786
modify /x b100 offset 2788  modify /x 4001 offset 2790
modify /x b000 offset 2792  modify /x 4001 offset 2794
modify /x b800 offset 2796  modify /x 4001 offset 2798
modify /x c000 offset 2800  modify /x 4001 offset 2802
modify /x c100 offset 2804  modify /x 4001 offset 2806
modify /x c000 offset 2808  modify /x 4001 offset 2810
modify /x c800 offset 2812  modify /x 4001 offset 2814
modify /x d000 offset 2816  modify /x 4001 offset 2818
modify /x d100 offset 2820  modify /x 4001 offset 2822
modify /x d000 offset 2824  modify /x 4001 offset 2826
modify /x d800 offset 2828  modify /x 4001 offset 2830
modify /x e000 offset 2832  modify /x 4001 offset 2834
modify /x e100 offset 2836  modify /x 4001 offset 2838
modify /x e000 offset 2840  modify /x 4001 offset 2842
modify /x e800 offset 2844  modify /x 4001 offset 2846

BBED> dump /v offset 2736 count 128
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets: 2736 to 2863  Dba:0x00000000
-------------------------------------------------------
 80004001 83004001 80004001 88004001 l ..@...@...@...@.
 90009000 91004001 90004001 98004001 l ......@...@...@.
 a0004001 a1004001 a0004001 a8004001 l
 b0004001 b1004001 b0004001 b8004001 l
 c0004001 c1004001 c0004001 c8004001 l
 d0004001 d1004001 d0004001 d8004001 l
 e0004001 e1004001 e0004001 e8004001 l
 00000000 00000000 00000000 00000000 l ................

修改完成这些后,我们就能够查到我们全部的数据了。

SQL> alter system flush buffer_cache;
System altered.

SQL> select count(1) from a1.a;
  COUNT(1)
----------
      2775

此时切勿执行一些其他的操作,应该尽快的使用CTAS的方式将这个表进行备份或者是导出。因为段头块L2和L1的信息还一些是没有修改的。

SQL> create table a2.a as select * from a1.a;
Table created.

SQL> drop table a1.a;
Table dropped

至此,Truncate恢复完成。仔细的研究才会发现,其实最重要的是摸清楚整个段的构造情况,只要你对整个段的构造情况,了若指掌,基本上恢复是很简单的。

Oracle Truncate table原理剖析二:从truncate结果看待恢复

总结一下前面,首先是段头块,它指向了L2块,L2块指向了L1块,而L1块则指向了我们真实的物理数据块。那么做了truncate操作,它到底做了什么鬼?这里需要测试一下,查看如下测试引入:

SQL> select OBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID from dba_objects where OBJECT_NAME='A';

OBJECT_NAME                     OBJECT_ID DATA_OBJECT_ID
------------------------------ ---------- --------------
A                                   75722          75722

我们先来看一下对象,当我们创建对象的之后,OBJECT_ID和data_object_id都会是一样的,但是当我们发生truncate之后,我们的object_id不会变,而data_object_id则会变掉。

SQL> truncate table a;
Table truncated.

SQL> select OBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID from dba_objects where OBJECT_NAME='A';

OBJECT_NAME                     OBJECT_ID DATA_OBJECT_ID
------------------------------ ---------- --------------
A                                   75722          75727

这里可以看到Truncate表之后,data_object_id从75722变成了75727。分别看一下段头块,L2位图块,L1位图块,数据块,这个ID是否有变化。分别从检查块130,129,224,225

BBED> set block 130
        BLOCK#          130

BBED> dump /v offset 272 count 20
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:  272 to  291  Dba:0x00000000
-------------------------------------------------------
 cf270100 00000010 80004001 08000000 l ......@.....
 00000000                            l .....

BBED> set block 129
BLOCK#          129

BBED> dump /v offset 104 count 20
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 129     Offsets:  104 to  123  Dba:0x00000000
-------------------------------------------------------
 cf270100 01000000 00000000 80004001 l ..........@.
 05000100                            l ....

BBED> set block 128
        BLOCK#          128

BBED>  dump /v offset 192 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 128     Offsets:  192 to  201  Dba:0x00000000
-------------------------------------------------------
 cf270100 e6151400 0000              l ..

BBED> set block 144
        BLOCK#          144

BBED> dump /v offset 192 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 144     Offsets:  192 to  201  Dba:0x00000000
-------------------------------------------------------
 ca270100 de300f00 0000              l ..

BBED> set block 160
        BLOCK#          160

BBED> dump /v offset 192 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 160     Offsets:  192 to  201  Dba:0x00000000
-------------------------------------------------------
 ca270100 de300f00 0000              l ..

BBED> set block 176
        BLOCK#          176

BBED> dump /v offset 192 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 176     Offsets:  192 to  201  Dba:0x00000000
-------------------------------------------------------
 ca270100 de300f00 0000              l ..

BBED> set block 192
        BLOCK#          192

BBED> dump /v offset 192 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 192     Offsets:  192 to  201  Dba:0x00000000
-------------------------------------------------------
 ca270100 de300f00 0000              l ..

 <16 bytes per line>

BBED> set block 208
        BLOCK#          208

BBED> dump /v offset 192 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 208     Offsets:  192 to  201  Dba:0x00000000
-------------------------------------------------------
 ca270100 de300f00 0000              l ..

 <16 bytes per line>

BBED> set block 224
        BLOCK#          224

BBED> dump /v offset 192 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 224     Offsets:  192 to  201  Dba:0x00000000
-------------------------------------------------------
 ca270100 de300f00 0000              l ..

BBED> set block 225
BLOCK#          225

BBED> dump /v offset 24 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 225     Offsets:   24 to   33  Dba:0x00000000
-------------------------------------------------------
 ca270100 90121400 0000              l ......

通过对各个块的dump,发现段头和L2位图块的data_obj_id已经发生了改变,从ca270100变成了cf270100,而只有第一个L1发生了变化,数据块则没有发生改变。其实到了这里,MDATA如何快速恢复truncate数据的原理非常清楚了.

那么如果从手工修复的角度来看的话,要处理的东西就比较多了,这方面可以参考第三节的内容。
也可以参考hellodba的《移花接木————利用Oracle表扫描机制恢复被Truncate的数据》

Oracle Truncate table原理剖析一:ASSM的三级位图实践解析

assm三级位图可以说是dba的基础技能知识了,这里用测试的方法验证下三级位图的原理。

1.建立实验环境所需要的表

SQL> create tablespace a1 datafile '/oracle/app/oracle/oradata/ora11/a1.dbf' size 20m;
Tablespace created.
SQL> create user a1 identified by a1 default tablespace a1;
User created.
SQL> grant dba to a1;
Grant succeeded.
SQL> connect a1/a1
Connected.
SQL> create table a as select * from dba_tables;
Table created.

实验环境建立完成之后,就需要摸清楚我们的这个段A的位图及数据块分布情况,我们不直接从视图里面查,这里我们考虑需要通过bbed的方式从数据块中获取位图及数据块分布情况。

2.段头块/L3位图块指向了哪些L2位图块

段由哪些区间构成?这个信息我们需要从段头块中获取出来。当你创建一个段后,即使你没有往里面插入任何数据,系统也是会预先分配一些区给你的。所以段头块是那个块,我们可以通过dba_segments查询出来。就算truncate了这个段,我们仍然能够从dba_segments中查询到段头的信息。

SQL> select HEADER_FILE,HEADER_BLOCK from dba_segments where SEGMENT_NAME='A' and owner='A1';
HEADER_FILE HEADER_BLOCK
----------- ------------
          5          130

找到了段头块,就可以用bbed挖掘下信息。

BBED> set filename '/oracle/app/oracle/oradata/ora11/a1.dbf'
        FILENAME        /oracle/app/oracle/oradata/ora11/a1.dbf
BBED> set block 130
        BLOCK#          130
BBED> dump /v offset 0 count 20
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets:    0 to   19  Dba:0x00000000
-------------------------------------------------------
 23a20000 82004001 d8321400 00000104 l #.@.....
 848d0000                            l ....

这里我们可以看到段头块的第一个offset是23。那么我们的段头块指向的L2位图块在offset 5192的位置。这里请记住段头块的标示是23。

BBED> dump /v offset 5192 count 100
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 130     Offsets: 5192 to 5291  Dba:0x00000000
-------------------------------------------------------
 81004001 00000000 00000000 00000000 l ..@.............
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000                            l ....

从这里我们既可以找到我们的L2位图块,81004001,这里只有1个L2块,因为后面都是00000000(空),因为涉及到操作系统字节序的问题,这里需要转换换成01400081。转换后我们可以使用下列查询找到文件号和块号。

SQL> select dbms_utility.data_block_address_file(to_number('01400081','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('01400081','xxxxxxxx')) as blockno from dual;

    FILENO    BLOCKNO
---------- ----------
         5        129

3.L2位图块指向了哪些L1位图块
接下来我们可以继续读我们的L2位图块来寻找我们的L1位图块。可以看到L2的第一个offset是21。请记住L2位图块的标示是21。

BBED> set block 129
        BLOCK#          129

BBED> dump /v offset 0 count 20
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 129     Offsets:    0 to   19  Dba:0x00000000
-------------------------------------------------------
 21a20000 81004001 d4321400 00000604 l !.@.....
 48a10000                            l H

L2指向L1数据块的位置从offset 116开始。到哪儿结束需要看后面有没有00000000(空)

BBED> dump /v offset 116 count 100
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 129     Offsets:  116 to  215  Dba:0x00000000
-------------------------------------------------------
 80004001 01000100 90004001 01000100 l ..@.......@.....
 a0004001 01000100 b0004001 01000100 l ........
 c0004001 01000100 d0004001 01000100 l ........
 e0004001 05000100 00000000 00000000 l ............
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000                            l ....

从dump出来的地方我们来看,前面是一个地址,后面跟着是01000100,比较规律,大概7组之后就变成00000000(空)了。 跟上面一样因为字节序的问题,这里我们需要将80004001转换成01400080。然后我们通过下列查询得到了区块的位置。而后面的01000100,前面的01则这个L1下面的块全部填满,无空数据块,后面的01则代表这个块是instance 1产生的。而最后的一个05000100,05则代表着这个L1下面还有空块,可以插入。而后面的01我们说过代表着是instance。如果这个系统是个rac的系统,节点2也插入了数据,那么这里就会显示05000200。

SQL> select dbms_utility.data_block_address_file(to_number('01400080','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('01400080','xxxxxxxx')) as blockno from dual
     union
     select dbms_utility.data_block_address_file(to_number('01400090','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('01400090','xxxxxxxx')) as blockno from dual
     union
     select dbms_utility.data_block_address_file(to_number('014000a0','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('014000a0','xxxxxxxx')) as blockno from dual
     union
     select dbms_utility.data_block_address_file(to_number('014000b0','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('014000b0','xxxxxxxx')) as blockno from dual
     union
     select dbms_utility.data_block_address_file(to_number('014000c0','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('014000c0','xxxxxxxx')) as blockno from dual
     union
     select dbms_utility.data_block_address_file(to_number('014000d0','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('014000d0','xxxxxxxx')) as blockno from dual
     union
     select dbms_utility.data_block_address_file(to_number('014000e0','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('014000e0','xxxxxxxx')) as blockno from dual;
    FILENO    BLOCKNO
---------- ----------
         5        128
         5        144
         5        160
         5        176
         5        192
         5        208
         5        224

7 rows selected.

通过上述查询,我们找到了7个L1块的信息。
4.L1位图块指向了哪些数据块
前面我们查到了我们的L2块上指向的L1块,并且清楚的知道哪个L1下面是满的,哪个L1下面还有空闲块。我们就从拿最后一个有空闲块的L1位图块进行分析。

BBED> set block 224
        BLOCK#          224

BBED> dump /v offset 0 count 10
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 224     Offsets:    0 to    9  Dba:0x00000000
-------------------------------------------------------
 20a20000 e0004001 d432              l  .@.
<16 bytes per line>

可以看到L1的第一个offset是20。请记住L1位图块的标示是20。
L1指向数据块的位置从offset 204开始。到哪儿结束需要看后面有没有00000000(空)

BBED> dump /v offset 204 count 80
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 224     Offsets:  204 to  283  Dba:0x00000000
-------------------------------------------------------
 e0004001 08000000 00000000 e8004001 l ........08000000 08000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................

大家可以看到这里的值是e0004001,08000000,00000000,下一组值是e8004001,08000000,08000000,然后面就是00000000(空)。
这两个的意思是告诉我们L1指向数据块的起始位置,比如e0004001,就是文件5,块224,也就是它自己本身。08000000就代表着这个块后面的连续7个块都是的。而e8004001,08000000,08000000,就是文件5,块232,08000000就代表着这个块后面的连续7个块也是的。而最后一个08000000则代表着offset,这里我们可以不用去管它。所以这里我们就能够知道我们的L1块下面具体的数据块有:
224(它本身),225,226,227,228,229,230,231,232,233,234,235,236,237,238,239。来用下列语句查证一下。

SQL> select distinct dbms_rowid.rowid_block_number(rowid) from a order by 1
................ ................ ................ ................ ................
                                 220
                                 221
                                 222
                                 223
                                 225
                                 226
                                 227
                                 228
                                 229
                                 230
                                 231
                                 232
                                 233

可以看到223后面直接就是225,直接此处跳空,这是因为我们的224是L1位图块,后面紧跟着我们刚刚说的225,226,227,228,229,230,231,232,233。但是问题是,这里看不到后面的234到239?这是因为234到239还是空闲没有格式化过的块,但是它已经被L1锁定了。那么我们的L1能不能看到这些情况呢?我们可以观察offset 396。

BBED> dump /v offset 396 count 50
 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0)
 Block: 224     Offsets:  396 to  445  Dba:0x00000000
-------------------------------------------------------
 11111111 11000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 0000                                l ..

这里可以看到的是11111111,11000000。那么这个代表什么意思呢?如果这个块是full的话,就是1,是unformatted的话就是0,正好和我们前面看到的吻合。

node参考:
Buddy Yuan《truncate》

现版本Mdata恢复工具在恢复oracle数据时候使用的考虑以及使用技巧

1.delete恢复的时候 会把历史的delete操作未被重用的数据块部分的数据恢复出来,所以做delete恢复的时候需要考虑到这点,当然需要恢复的数据可以通过刷选出来

2.truncate恢复时候是可以选择是或者否恢复出delete的数据的,这点可以同通过contain的方式实现

3.lob inindex损坏的时候,lob的数据恢复就成了比较大的难题

4.坏块部分目前mdata没有做处理碰到坏块就异常退出程序,比如像dul在09年时候也未处理,当然现在的支持是比较好的。后续的mdata 4.0版本会对坏块做略过处理,并增加坏块评估统计的功能,mdata 4.0研发重点会在坏块的处理上

5.虽然目前我们已经有scan的功能,也可以直接通过block(1.4版本) scan的方式恢复出对应的数据,但是在asm中,这部分内容需要做优化和调整,现有的mdata3.6可以通过在asm file 1保存完好的情况下直接读asm disk恢复出datafile,功能类似amdu,(同时从asm直接恢复出表的功能我们也已经开发完毕,4.0中发布),如果file 1有损坏则在针对asm的disk做datafile的直接恢复时候做特殊处理,所以当前版本的scan建议就只用在非asm格式下的恢复。如果asm file 1还保存完好,那么也可以通过mdata3.6版本恢复出对应的数据文件.直接scan出datafile的功能是用在asm file 1损害的情况下,为了尽最大程度恢复数据的情况下使用。mdata后续的版本更新中会持续改善这类的支持。

6.恢复数据的scan tablespace小技巧
可以并行scan 扫描增加扫描速度;
可以对大的单表数据在unload的数据方式上选择dmp,这样的话在导入时候可以增加效率;
没事可以到ddl目录下逛逛,没准有发现。

当然了这个工具持续更新了一年,后续我和hc也会持续的更新,有任何问题也可以联系我和hc,在修复工具的使用问题上,非常欢迎大家联系我们。

从ASM DISKS中定位表行在系统DISK中的物理偏移位置

该部分也是承接从ASM直接读取数据的研究思考,主要是根据asm实例中的x$kffxp视图确定au的位置。

首先准备测试环境的脚本,数据块大小默认为8l:

SQL script to create tablespace
col   "File name" format a60
col "Tablespace name" format a20
--创建表空间:
connect sys/ludatou as sysdba
create tablespace test  datafile '+TEST' size 5m;
grant dba to luda;
--创建测试表:
connect luda/luda
create table luda_tab (n number, name varchar2(16)) tablespace test;
insert into luda_tab values (1, 'ASM_TEST');
commit;
select ROWID, NAME from luda_tab;

通过以上查询可以知道luda_tab的行块所在位置为数据文件的133号数据块。

SQL> select ROWID from luda_tab;

ROWID
------------------
AAAXgDAAHAAAACFAAA

SQL> select DBMS_ROWID.ROWID_BLOCK_NUMBER('AAAXgDAAHAAAACFAAA') "Block number" from DUAL;

Block number
------------
133

 
数据库块大小为8k

SQL> show parameter db_block_size

NAME                     TYPE     VALUE
------------------------------------ ----------- ------------------------------
db_block_size                 integer     8192

TEST表空间的数据文件ASM file number为256

SQL> select f.FILE#, f.NAME "File name", t.NAME "Tablespace name" from V$DATAFILE f, V$TABLESPACE t where t.NAME='TEST' and f.TS# = t.TS#;

FILE# File name                                       Tablespace name
---------- -------------------------------------------------- ---------------
7 +TEST/racnode1/datafile/test.256.852905863          TEST

TEST表空间所在的diskgroup号为4

SQL> select GROUP_NUMBER from V$ASM_DISKGROUP where NAME='TEST';

GROUP_NUMBER
------------
       4

4号diskgroup的au size为1M

SQL> select VALUE from V$ASM_ATTRIBUTE where NAME='au_size' and GROUP_NUMBER=4;

VALUE
------------------------------------------------------------------------------
1048576

Diskgroup 4的磁盘信息

SQL> select GROUP_NUMBER, DISK_NUMBER, NAME, path from V$ASM_DISK  where  GROUP_NUMBER=4;
GROUP_NUMBER DISK_NUMBER NAME                PATH
------------ ----------- ------------------------------ ------------------------------
       4           0 TEST_0000            /dev/asm_disk_1
       4           1 TEST_0001            /dev/asm_disk_2

AU分部与OS磁盘的对应信息

select PXN_KFFXP, -- physical extent number
  XNUM_KFFXP, -- virtual extent number
  DISK_KFFXP, -- disk number
  AU_KFFXP    -- allocation unit number
from X$KFFXP
where NUMBER_KFFXP=256 -- ASM file 256
AND GROUP_KFFXP=4 -- group number 4
order by 1;

 PXN_KFFXP XNUM_KFFXP DISK_KFFXP   AU_KFFXP
---------- ---------- ---------- ----------
     0        0           0    144
     1        0           1    144
     2        1           1    145
     3        1           0    145
     4        2           0    146
     5        2           1    146
     6        3           1    147
     7        3           0    147
     8        4           0    148
     9        4           1    148
    10        5           1    149
    11        5           0    149

从上面的au分布与os磁盘的对应信息中可以看到256号asm file一共有12个au,au size为1m。由于diskgroup为normal reduncancy模式,所以每一个au在1号磁盘中还有一个镜像,所以一共是12个AU,占用12M。blocksize为8k,所以每个au包含的block数量为1024/8=128个,因为行块所在位置为133号数据块,所以该块应该处于数据文件的第二个au的5(133-128)号8k单元数据块,也就是磁盘/dev/asm_disk_1的145号AU,仔细观察可以发现extent也为对应的号数,由于冗余所以每个extent都有2个。

接下来确认行块在asm_disk_1以及asm_disk_2上

[grid@racnode1 luda]$  strings  /dev/asm_disk_1 | grep ASM_TEST
ASM_TEST
[grid@racnode1 luda]$ strings   /dev/asm_disk_2 | grep ASM_TEST
ASM_TEST

接着使用dd导出磁盘中145号AU的数据

[grid@racnode1 luda]# dd if=/dev/asm_disk_1  bs=1024k count=1 skip=144 of=AU_145.data
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.031567 seconds, 21.3 MB/s
[grid@racnode1 luda]$
[grid@racnode1 luda]$ ls -l AU_145.data
-rw-r--r-- 1 grid oinstall 1048576 Aug 13 22:45 AU_145.data
[grid@racnode1 luda]$

下来就可以从145号au中取出第5个8k块的数据

[grid@racnode1 luda]$ dd if=AU_145.data bs=8k count=1 skip=4 of=block133.data

使用od观察该块即可找到我们想要的数据ASM_TEST。

[grid@racnode1 luda]$ od -c block133.data
0137760 002 301 002  \b   A   S   M   _   T   E   S   T 001 006   i 356Looks good

到此通过AU与OS磁盘的映射关系,验证了可以找到对应行的数据。解释了如何通过asm直接读取object的基本原理。