利用Oracle表扫描机制恢复被Truncate的数据

几天前,用FySafe帮朋友恢复了一个测试环境(无备份、无归档)中被意外TRUNCATE的一张表的数据。FySafe是一个类似DUL的工具,通过逐个解析数据块内容来恢复数据。朋友问,在这种情况下,还有没有更加迅捷的方法来恢复数据?我说目前还没有。
后来又仔细考虑一下这个问题。觉得从理论上讲应该可以找到另外一个方法来恢复数据。
首先,我们分析一下TRUNCATE的过程。TRUNCATE不会逐个清除用户数据块上的数据,而仅仅重置数据字典和元数据块上的元数据(如存储段头和扩展段图)。也就是说,此时,其基本数据并未被破坏,而是被系统回收、等待被重新分配————因此,要恢复被TRUNCATE的数据,需要及时备份其所在的数据文件。
然后,再分析一下表扫描的过程(我曾经在这篇文章中分析了小表扫描的过程:oracle逻辑IO的秘密: Full Table Scan: Part 1):Oracle会读取段头的元数据,获得高水位线等信息,然后读取高水位线以下被格式化的数据块。因此,理论上讲,如果能够将被重置的元数据和元数据块重新构造出来,就能使数据能被重新读取。然而,要完成这个任务,难度相当大————要找出原有的所有元数据块被保证其每个字节与被TRUANCATE之前完全相同————看起来似乎是一个不可能完成的任务。
不过,我们可以换一角度来找方法————如果我们已经有一套元数据及数据块,然后将被TRUNCATE的用户数据块的内容取代其用户数据块的内容,是否可以“骗”过Oracle,让它读出这些数据呢?
回顾一下表扫描的过程,这个方法应该是可行的。我们只要想办法构造出一个结构相同、且具有完整元数据信息和格式化了的用户数据块的傀儡表对象,然后将被TRUNCATE的用户数据块找出,再将其数据内容部分嫁接到傀儡对象的用户数据块,使Oracle以外这是傀儡对象的数据,就能让Oracle扫描并读出数据内容。其原理用图示描述如下:

XML/HTML代码
  1.                                                 +-------------------------+ 
  2.                                                 | Copy Of Dummy Data File | 
  3.                                                 |  (With Formmated Blocks)| 
  4.                                                 +-------------------------+ 
  5.                                                             || 
  6.                                                             \/ 
  7.                                                 (Blcok Header, Block Tail) 
  8.                                                             || 
  9.                                                             \/ 
  10. +-------------------+                                +----------------+     Table Scan    +---------------+ 
  11. | Source Data File  | => (Data Block Content) =>     |  Dummy Table   |    ============>  | Restore Table | 
  12. |(Without Meta Data)|                                |(With Meta Data)|                   +---------------+ 
  13. +-------------------+                                +----------------+ 

按照这个原理,我创建了PLSQL包Fy_Recover_Data,并成功恢复了被TRUNCATE的数据:

SQL代码
  1. HELLODBA.COM>insert into demo.truntab select * from demo.t_objects; 
  2.  
  3. 47585 rows created. 
  4.  
  5. HELLODBA.COM>commit
  6.  
  7. Commit complete. 
  8.  
  9. HELLODBA.COM>select count(*) from demo.truntab; 
  10.  
  11.   COUNT(*) 
  12. ---------- 
  13.      47585 
  14.  
  15. HELLODBA.COM>truncate table demo.truntab; 
  16.  
  17. Table truncated. 
  18.  
  19. HELLODBA.COM>declare 
  20.   2    tgtowner varchar2(30); 
  21.   3    tgttable varchar2(30); 
  22.   4    datapath varchar2(4000); 
  23.   5    datadir varchar2(30); 
  24.   6    rects varchar2(30); 
  25.   7    recfile varchar2(30); 
  26.   8    rstts varchar2(30); 
  27.   9    rstfile varchar2(30); 
  28. 10    blksz number; 
  29. 11    rectab varchar2(30); 
  30. 12    rsttab varchar2(30); 
  31. 13    copyfile varchar2(30); 
  32. 14  begin 
  33. 15    tgtowner := 'DEMO'
  34. 16    tgttable := 'TRUNTAB'
  35. 17    datapath := 'D:\oracle\product\10.2.0\oradata\EDGAR\DATAFILE\';
  36. 18    datadir := 'FY_DATA_DIR'; 
  37. 19    Fy_Recover_data.prepare_files(tgtowner, tgttable, datapath, datadir, rects, recfile, rstts, rstfile, blksz); 
  38. 20    Fy_Recover_data.fill_blocks(tgtowner, tgttable, datadir, rects, recfile, rstts, 8, tgtowner, tgtowner, rectab, rsttab, copyfile); 
  39. 21    Fy_Recover_data.recover_table(tgtowner, tgttable, tgtowner, rectab, tgtowner, rsttab, datadir, datadir, recfile,datadir, copyfile, blksz); 
  40. 22  end
  41. 23  / 
  42. Directory Name: FY_DATA_DIR 
  43. Recover Tablespace: FY_REC_DATA; Data File: FY_REC_DATA.DAT 
  44. Restore Tablespace: FY_RST_DATA; Data File: FY_RST_DATA.DAT 
  45. Recover Table: DEMO.TRUNTAB$ 
  46. Restore Table: DEMO.TRUNTAB$$ 
  47. Data Blocks formatted. 
  48. Copy file of Recover Tablespace: FY_REC_DATA_COPY.DAT 
  49. 373 records recovered 
  50. 328 records recovered 
  51. 334 records recovered 
  52. .. ... 
  53. 285 records recovered 
  54. 275 records recovered 
  55. 235 records recovered 
  56. 47585 records recovered in backup table DEMO.TRUNTAB$$ 
  57.  
  58. PL/SQL procedure successfully completed. 
  59.  
  60. HELLODBA.COM>insert into demo.truntab select * from DEMO.TRUNTAB$$; 
  61.  
  62. 47585 rows created. 
  63.  
  64. HELLODBA.COM>commit
  65.  
  66. Commit complete. 

数据全部恢复。我们再测试一下压缩表的恢复:

SQL代码
  1. HELLODBA.COM>set serveroutput on format wrapped 
  2. HELLODBA.COM>insert into demo.truntab select * from demo.t_objects; 
  3.  
  4. 47585 rows created. 
  5.  
  6. HELLODBA.COM>commit
  7.  
  8. Commit complete. 
  9.  
  10. HELLODBA.COM>alter table demo.truntab move compress; 
  11.  
  12. Table altered. 
  13.  
  14. HELLODBA.COM>select count(*) from demo.truntab; 
  15.  
  16.   COUNT(*) 
  17. ---------- 
  18.      95170 
  19.  
  20. HELLODBA.COM>truncate table demo.truntab; 
  21.  
  22. Table truncated. 
  23.  
  24. HELLODBA.COM>declare 
  25.   2    tgtowner varchar2(30); 
  26.   3    tgttable varchar2(30); 
  27.   4    datapath varchar2(4000); 
  28.   5    datadir varchar2(30); 
  29.   6    rects varchar2(30); 
  30.   7    recfile varchar2(30); 
  31.   8    rstts varchar2(30); 
  32.   9    rstfile varchar2(30); 
  33. 10    blksz number; 
  34. 11    rectab varchar2(30); 
  35. 12    rsttab varchar2(30); 
  36. 13    copyfile varchar2(30); 
  37. 14  begin 
  38. 15    tgtowner := 'DEMO'
  39. 16    tgttable := 'TRUNTAB'
  40. 17    --datapath := 'D:\oracle\product\10.2.0\oradata\EDGAR\DATAFILE\'; 
  41. 18    datadir := 'FY_DATA_DIR';
  42. 19    --prepare_files(tgtowner, tgttable, datapath, datadir, rects, recfile, rstts, rstfile, blksz);
  43. 20    rects := 'FY_REC_DATA';
  44. 21    rstts := 'FY_RST_DATA';
  45. 22    recfile := 'FY_REC_DATA.DAT'; 
  46. 23    Fy_Recover_data.clean_up_ts(rects, rstts); 
  47. 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; 
  48. 25    blksz := 8192; 
  49. 26    Fy_Recover_data.fill_blocks(tgtowner, tgttable, datadir, rects, recfile, rstts, 8, tgtowner, tgtowner, rectab, rsttab, copyfile); 
  50. 27    Fy_Recover_data.recover_table(tgtowner, tgttable, tgtowner, rectab, tgtowner, rsttab, datadir, datadir, recfile,datadir, copyfile, blksz); 
  51. 28  end
  52. 29  / 
  53. Recover Table: DEMO.TRUNTAB$ 
  54. Restore Table: DEMO.TRUNTAB$$ 
  55. Data Blocks formatted. 
  56. Copy file of Recover Tablespace: FY_REC_DATA_COPY.DAT 
  57. 965 records recovered 
  58. 958 records recovered 
  59. 1144 records recovered 
  60. ... ... 
  61. 655 records recovered 
  62. 662 records recovered 
  63. 97 records recovered 
  64. 95170 records recovered in backup table DEMO.TRUNTAB$$ 
  65.  
  66. PL/SQL procedure successfully completed. 

同样可以恢复。
利用这个包,应该可以恢复大多数情况下被TRUNCATE掉的数据。并且,还能实现部分FySafe的功能。例如,损毁的数据库的SYSTEM表空间可用的情况下,可以先从SYSTEM表空间恢复数据字典表(对每个数据库版本来说,每个数据字典表的结构和数据对象编号是固定的),然后由数据字典表中信息创建傀儡表,利用表扫描恢复用户数据表。但是,相对FySafe这样比较完善的恢复工具来说,Fy_Recover_Data还是存在一些不足的地方。例如,在没有元数据可用的情况下,FySafe可以猜测出表结构进行数据恢复,而Fy_Recover_Data则无法完成恢复。简单比较如下:

XML/HTML代码
  1.                 Fy_Recover_Data     FySafe 
  2. 单表恢复效率    高                  高 
  3. 多表恢复效率    低                  高 
  4. 压缩表          支持                支持 
  5. 索引组织表      支持                支持 
  6. 分区表          支持                支持 
  7. 行链接/行迁移   不支持              支持 
  8. 标准SQL类型     支持                支持 
  9. BLOB/CLOB       支持Store in Row    支持 
  10. 删除记录恢复    不支持              支持 
  11. 离线恢复        不支持              支持 
  12. 无元数据恢复    不支持              支持 
  13. 操作系统平台    全部                全部 
  14. 数据库版本      9i以上              8i以上 
  15. 本地管理表空间  支持                支持 
  16. 数据字典表空间  未测试              支持 
  17. 其他需求        不需要              Java 
PRM DUL for oracle恢复truncate截断掉的 Oracle DBA神器:PRM灾难恢复工具,Schema级别数据恢复。PRM For Oracle Database – schema级别oracle数据数据恢复特性 ,PRM即ParnassusData Recovery Manager是企业级别Oracle数据库灾难恢复工具。PRM可以在无备份的情况下恢复truncated/drop掉的,也可以恢复无法打开的Oracle数据库(Alter Database Open失败)中的数据。 PRM是图形化增强版的Oracle DUL工具,同时具备很多Oracle DUL不具备的特性 情况 当某张被意外truncated掉了,需要恢复其上的所有数据时。空间的多个数据文件均存放在ASM上,且没有任何形式的备份。 注意这边文章针对的是PRM在 数据字典模式下的Truncate恢复选项不可用时使用,数据字典模式下的Truncate恢复选项是最简单、易用的一种模式,具体使用见《使用PRM恢复Oracle数据库中误truncate截断的数据》http://www.parnassusdata.com/zh-hans/node/52 PRM 3.0的下载地址: http://parnassusdata.com/sites/default/files/ParnassusData_PRMForOracle_3002.zip PRM 的官方网站: http://www.parnassusdata.com/ PRM背景 PRM恢复数据时存在多种模式, PRM需要知道哪些上的数据块是需要被读取并取出数据的。默认的现形式是直接从segment header数据段头里获取EXTENT MAP即盘区图,另一种方案就是由PRM自己去构建一个盘区图。 这些盘区图可以通过,PRM的SCAN DATABASE选项来获得: Recovery Wizard => Non-Dictionary Mode,如果是ASM则选择Non-Dictionary Mode(ASM) 执行SCAN Database后会生成SEG$和EXT$的数据到PRM内嵌的数据库中,之后可以选择SCAN TABLES FROM SEGMENTS 或者 SCAN TABLES FROM EXTENTS。 FROM Segments 意味着使用Segment Header中获得的Extent MAP信息,而FROM Extents意味着使用PRM自己扫描获得的EXTENT信息。 请注意当TRUNCATE发生后, 数据Table的Segment Header中的Extent MAP信息就会被清空了, 但实际存放数据数据块中的行数据还是在哪里的,除非被其他数据/索引的增长而覆盖了。 所以当Truncate发生后选择SCAN TABLES FROM SEGMENT 是找不回数据的,必须使用SCAN TABLES FROM EXTENTS, EXTENT的信息是PRM自己去数据文件中扫描获得的,所以只要有数据的地方PRM就会自己去找到。 除了Truncate需要使用到 SCAN TABLES FROM EXTENTS之外对于DROP TABLE的恢复也可以用到SCAN TABLES FROM EXTENTS , 总之当Segment Header找不到(可能存放Segment Header的数据文件丢失了)、或者已损坏(可能Segment Header的数据块被损坏了)、或者其中的Extent Map数据无效(Truncate、DROP或逻辑损坏)时都可以使用SCAN TABLES FROM EXTENTS 。 但是如果不存在上述的问题时,建议用SCAN TABLES FROM SEGMENTS ,因为从Segment Header获取信息更方便也更高效一些。 在PRM中同一个程序实例 同时只能使用SCAN TABLES FROM SEGMENTS 或者 SCAN TABLES FROM EXTENTS 中的一个。 使用SCAN TABLES FROM EXTENTS 后需要找到对应被TRUNCATE掉的的原始DATA_OBJECT_ID,即左侧属性图中的一个对象,并将其DataBridge 数据搭桥传输到目标数据库中即可。 用户truncate误删 schema下的若干数据,无法使用flashback query等技术恢复数据,尝试从之前的全备份中恢复数据库restore速度较快,但是archivelog恢复时由于HP data Protecter的不明原因导致归档恢复十分缓慢,缓慢一个归档往往要几分钟,而需要restore数百个归档,时间上无法接受。 该案例通过PRM-DUL直接在字典模式下恢复truncate数据的功能,在不到一个小时内就恢复了数十万条数据,虽然我们无法保证不丢失一条数据,但至少帮助用户在最短时间内恢复了主要业务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值