[English]
作者:
fuyuncat
来源:
www.HelloDBA.com
几天前,用FySafe帮朋友恢复了一个测试环境(无备份、无归档)中被意外TRUNCATE的一张表的数据。FySafe是一个类似DUL的工具,通过逐个解析数据块内容来恢复数据。朋友问,在这种情况下,还有没有更加迅捷的方法来恢复数据?我说目前还没有。
后来又仔细考虑一下这个问题。觉得从理论上讲应该可以找到另外一个方法来恢复数据。
首先,我们分析一下TRUNCATE的过程。TRUNCATE不会逐个清除用户数据块上的数据,而仅仅重置数据字典和元数据块上的元数据(如存储段头和扩展段图)。也就是说,此时,其基本数据并未被破坏,而是被系统回收、等待被重新分配————因此,要恢复被TRUNCATE的数据,需要及时备份其所在的数据文件。
然后,再分析一下表扫描的过程(我曾经在这篇文章中分析了小表扫描的过程:oracle逻辑IO的秘密: Full Table Scan: Part 1):Oracle会读取段头的元数据,获得高水位线等信息,然后读取高水位线以下被格式化的数据块。因此,理论上讲,如果能够将被重置的元数据和元数据块重新构造出来,就能使数据能被重新读取。然而,要完成这个任务,难度相当大————要找出原有的所有元数据块被保证其每个字节与被TRUANCATE之前完全相同————看起来似乎是一个不可能完成的任务。
不过,我们可以换一角度来找方法————如果我们已经有一套元数据及数据块,然后将被TRUNCATE的用户数据块的内容取代其用户数据块的内容,是否可以“骗”过Oracle,让它读出这些数据呢?
回顾一下表扫描的过程,这个方法应该是可行的。我们只要想办法构造出一个结构相同、且具有完整元数据信息和格式化了的用户数据块的傀儡表对象,然后将被TRUNCATE的用户数据块找出,再将其数据内容部分嫁接到傀儡对象的用户数据块,使Oracle以外这是傀儡对象的数据,就能让Oracle扫描并读出数据内容。其原理用图示描述如下:XML/HTML代码
- +-------------------------+
- | Copy Of Dummy Data File |
- | (With Formmated Blocks)|
- +-------------------------+
- ||
- \/
- (Blcok Header, Block Tail)
- ||
- \/
- +-------------------+ +----------------+ Table Scan +---------------+
- | Source Data File | => (Data Block Content) => | Dummy Table | ============> | Restore Table |
- |(Without Meta Data)| |(With Meta Data)| +---------------+
- +-------------------+ +----------------+
按照这个原理,我创建了PLSQL包Fy_Recover_Data,并成功恢复了被TRUNCATE的数据:
SQL代码
- HELLODBA.COM>insert into demo.truntab select * from demo.t_objects;
- 47585 rows created.
- HELLODBA.COM>commit;
- Commit complete.
- HELLODBA.COM>select count(*) from demo.truntab;
- COUNT(*)
- ----------
- 47585
- HELLODBA.COM>truncate table demo.truntab;
- Table truncated.
- HELLODBA.COM>declare
- 2 tgtowner varchar2(30);
- 3 tgttable varchar2(30);
- 4 datapath varchar2(4000);
- 5 datadir varchar2(30);
- 6 rects varchar2(30);
- 7 recfile varchar2(30);
- 8 rstts varchar2(30);
- 9 rstfile varchar2(30);
- 10 blksz number;
- 11 rectab varchar2(30);
- 12 rsttab varchar2(30);
- 13 copyfile varchar2(30);
- 14 begin
- 15 tgtowner := 'DEMO';
- 16 tgttable := 'TRUNTAB';
- 17 datapath := 'D:\oracle\product\10.2.0\oradata\EDGAR\DATAFILE\';
- 18 datadir := 'FY_DATA_DIR';
- 19 Fy_Recover_data.prepare_files(tgtowner, tgttable, datapath, datadir, rects, recfile, rstts, rstfile, blksz);
- 20 Fy_Recover_data.fill_blocks(tgtowner, tgttable, datadir, rects, recfile, rstts, 8, tgtowner, tgtowner, rectab, rsttab, copyfile);
- 21 Fy_Recover_data.recover_table(tgtowner, tgttable, tgtowner, rectab, tgtowner, rsttab, datadir, datadir, recfile,datadir, copyfile, blksz);
- 22 end;
- 23 /
- Directory Name: FY_DATA_DIR
- Recover Tablespace: FY_REC_DATA; Data File: FY_REC_DATA.DAT
- Restore Tablespace: FY_RST_DATA; Data File: FY_RST_DATA.DAT
- Recover Table: DEMO.TRUNTAB$
- Restore Table: DEMO.TRUNTAB$$
- Data Blocks formatted.
- Copy file of Recover Tablespace: FY_REC_DATA_COPY.DAT
- 373 records recovered
- 328 records recovered
- 334 records recovered
- .. ...
- 285 records recovered
- 275 records recovered
- 235 records recovered
- 47585 records recovered in backup table DEMO.TRUNTAB$$
- PL/SQL procedure successfully completed.
- HELLODBA.COM>insert into demo.truntab select * from DEMO.TRUNTAB$$;
- 47585 rows created.
- HELLODBA.COM>commit;
- Commit complete.
数据全部恢复。我们再测试一下压缩表的恢复:
SQL代码
- HELLODBA.COM>set serveroutput on format wrapped
- HELLODBA.COM>insert into demo.truntab select * from demo.t_objects;
- 47585 rows created.
- HELLODBA.COM>commit;
- Commit complete.
- HELLODBA.COM>alter table demo.truntab move compress;
- Table altered.
- HELLODBA.COM>select count(*) from demo.truntab;
- COUNT(*)
- ----------
- 95170
- HELLODBA.COM>truncate table demo.truntab;
- Table truncated.
- HELLODBA.COM>declare
- 2 tgtowner varchar2(30);
- 3 tgttable varchar2(30);
- 4 datapath varchar2(4000);
- 5 datadir varchar2(30);
- 6 rects varchar2(30);
- 7 recfile varchar2(30);
- 8 rstts varchar2(30);
- 9 rstfile varchar2(30);
- 10 blksz number;
- 11 rectab varchar2(30);
- 12 rsttab varchar2(30);
- 13 copyfile varchar2(30);
- 14 begin
- 15 tgtowner := 'DEMO';
- 16 tgttable := 'TRUNTAB';
- 17 --datapath := 'D:\oracle\product\10.2.0\oradata\EDGAR\DATAFILE\';
- 18 datadir := 'FY_DATA_DIR';
- 19 --prepare_files(tgtowner, tgttable, datapath, datadir, rects, recfile, rstts, rstfile, blksz);
- 20 rects := 'FY_REC_DATA';
- 21 rstts := 'FY_RST_DATA';
- 22 recfile := 'FY_REC_DATA.DAT';
- 23 Fy_Recover_data.clean_up_ts(rects, rstts);
- 24 --select block_size into blksz from dba_tablespaces ts, dba_tables t where ts.tablespace_name = t.tablespace_name and t.owner = tgtowner and t.table_name = tgttable;
- 25 blksz := 8192;
- 26 Fy_Recover_data.fill_blocks(tgtowner, tgttable, datadir, rects, recfile, rstts, 8, tgtowner, tgtowner, rectab, rsttab, copyfile);
- 27 Fy_Recover_data.recover_table(tgtowner, tgttable, tgtowner, rectab, tgtowner, rsttab, datadir, datadir, recfile,datadir, copyfile, blksz);
- 28 end;
- 29 /
- Recover Table: DEMO.TRUNTAB$
- Restore Table: DEMO.TRUNTAB$$
- Data Blocks formatted.
- Copy file of Recover Tablespace: FY_REC_DATA_COPY.DAT
- 965 records recovered
- 958 records recovered
- 1144 records recovered
- ... ...
- 655 records recovered
- 662 records recovered
- 97 records recovered
- 95170 records recovered in backup table DEMO.TRUNTAB$$
- PL/SQL procedure successfully completed.
同样可以恢复。
利用这个包,应该可以恢复大多数情况下被TRUNCATE掉的数据。并且,还能实现部分FySafe的功能。例如,损毁的数据库的SYSTEM表空间可用的情况下,可以先从SYSTEM表空间恢复数据字典表(对每个数据库版本来说,每个数据字典表的结构和数据对象编号是固定的),然后由数据字典表中信息创建傀儡表,利用表扫描恢复用户数据表。但是,相对FySafe这样比较完善的恢复工具来说,Fy_Recover_Data还是存在一些不足的地方。例如,在没有元数据可用的情况下,FySafe可以猜测出表结构进行数据恢复,而Fy_Recover_Data则无法完成恢复。简单比较如下:XML/HTML代码
- Fy_Recover_Data FySafe
- 单表恢复效率 高 高
- 多表恢复效率 低 高
- 压缩表 支持 支持
- 索引组织表 支持 支持
- 分区表 支持 支持
- 行链接/行迁移 不支持 支持
- 标准SQL类型 支持 支持
- BLOB/CLOB 支持Store in Row 支持
- 删除记录恢复 不支持 支持
- 离线恢复 支持 支持
- 无元数据恢复 不支持 支持
- 操作系统平台 全部 全部
- 数据库版本 9i以上 8i以上
- 本地管理表空间 支持 支持
- 数据字典表空间 未测试 支持
- 其他需求 不需要 Java
最新版的源代码可以在这里下载: Fy_Recover_Data
--- Fuyuncat ---
上一篇:Oracle 11g 直接IO自动调节 | 下一篇:即将到来... ... |
本类中的所有文章 |