ORACLE恢复truncate的表-转载

这两天都在研究怎么恢复被清空过的表,恢复的方法很多,可用的工具也很多,在这里分享一下个人认为比较方便的一种方法:
 
首先,我们分析一下TRUNCATE的过程。TRUNCATE不会逐个清除用户数据块上的数据,而仅仅重置数据字典和元数据块上的元数据(如存储段头和扩展段图)。也就是说,此时,其基本数据并未被破坏,而是被系统回收、等待被重新分配————因此,要恢复被TRUNCATE的数据,需要及时备份其所在的数据文件。
然后,再分析一下表扫描的过程:Oracle会读取段头的元数据,获得高水位线等信息,然后读取高水位线以下被格式化的数据块。因此,理论上讲,如果能够将被重置的元数据和元数据块重新构造出来,就能使数据能被重新读取。然而,要完成这个任务,难度相当大————要找出原有的所有元数据块被保证其每个字节与被TRUANCATE之前完全相同————看起来似乎是一个不可能完成的任务。
不过,我们可以换一角度来找方法————如果我们已经有一套元数据及数据块,然后将被TRUNCATE的用户数据块的内容取代其用户数据块的内容,是否可以“骗”过Oracle,让它读出这些数据呢?
回顾一下表扫描的过程,这个方法应该是可行的。我们只要想办法构造出一个结构相同、且具有完整元数据信息和格式化了的用户数据块的傀儡表对象,然后将被TRUNCATE的用户数据块找出,再将其数据内容部分嫁接到傀儡对象的用户数据块,使Oracle以外这是傀儡对象的数据,就能让Oracle扫描并读出数据内容。

 恢复方法:新建两个表空间,分别存放A表和B表,表结构与源表一致。A表用于构造元数据,B表用于存放恢复数据。读取A表的元数据与源表的数据块,存放到新的数据文件。

调用语句:
declare  
    tgtowner varchar2(30);  
    tgttable varchar2(30);  
    datapath varchar2(4000);  
    datadir varchar2(30);  
    rects varchar2(30);  
    recfile varchar2(30);  
    rstts varchar2(30);  
    rstfile varchar2(30);  
    blksz number;  
    rectab varchar2(30);  
    rsttab varchar2(30);  
    copyfile varchar2(30);  
  begin  
    tgtowner := 'LAN'; --用户  
    tgttable := 'TEST1';  --要恢复的表名
    datapath := '/u01/app/oracle/oradata/db1/'; --数据文件所在的目录
    datadir := 'FY_DATA_DIR';   ---可保持不变
    pg_lan_recover_tab_data.prepare_files(tgtowner, tgttable, datapath, datadir, rects, recfile, rstts, rstfile, blksz);  
    pg_lan_recover_tab_data.fill_blocks(tgtowner, tgttable, datadir, rects, recfile, rstts, 8, tgtowner, tgtowner, rectab, rsttab, copyfile);  
    pg_lan_recover_tab_data.recover_table(tgtowner, tgttable, tgtowner, rectab, tgtowner, rsttab, datadir, datadir, recfile,datadir, copyfile, blksz);  
  end;   

说明:如果存储用的是ASM,需将数据文件复制到数据库主机。 此代码可以恢复非分区表,可恢复普通压缩和非压缩表,如需恢复分区表与混合列压缩表,请与本人联系。

程序 代码:(需DBA与select any dictionary权限)
 create or replace package pg_lan_recover_tab_data authid current_user  is
  ---------------------------------------------------------------------------
  /* 需要权限:1、DBA角色;2、select and dictionary
 
  */
  ---------------------------------------------------------------------------
  type r_cursor is REF CURSOR;
  type o_fileprop is record (
   file# number,
   status$ number);
  type t_fileprops is table of o_fileprop;
  /************************************************************************
  ** Copy file
  **
  ** srcdir: Directory of Source File;
  ** srcfile: Source File Name;
  ** dstdir: Directory of Destination File;
  ** dstfile: Destination File Name;
  ************************************************************************/
  procedure copy_file(srcdir varchar2,
                      srcfile varchar2,
                      dstdir varchar2 default null,
                      dstfile varchar2 default null);
  /************************************************************************
  ** Recover Table Data From Special Data File;
  **
  ** oriobjid: Object Id of Table to be Recovered;
  ** recowner: Owner of Table to be used as recovering dummy table;
  ** rectable: Name of Table to be used as recovering dummy table;
  ** rstowner: Owner of Table to store the recovered data;
  ** rsttable: Name of Table to store the recovered data;
  ** srcdir: Directory of the Data File to be recovered;
  ** srcfile: Name of the Data File to be recovered;
  ** recdir: Directory of Data File that rectable is stored;
  ** recfile: Name of Data File that rectable is stored;
  ** copydir: Directory of Copy of Data File that rectable is stored;
  ** coryfile: Name of Copy of Data File that rectable is stored;
  ** blksz: Block size of the Tablespace Storing the Table to be recovered;
  ** selflink: database link refer to instance self connect to dba account;
  ************************************************************************/
  procedure recover_table(oriobjid number,
                          recowner varchar2,
                          rectab varchar2,
                          rstowner varchar2,
                          rsttab varchar2,
                          srcdir varchar2,
                          srcfile varchar2,
                          recdir varchar2,
                          recfile varchar2,
                          copydir varchar2,
                          copyfile varchar2,
                          blksz number default 8192,
                          fillblks number default 5,
                          selflink varchar2 default '',
                          endianess number default 1);
  /************************************************************************
  ** Recover Table Data From Data Files of Targe Table;
  **
  ** tgtowner: Owner of Target Table to be recovered;
  ** tgttable: Name of Target Table to be recovered;
  ** recowner: Owner of Table to be used as recovering dummy table;
  ** rectable: Name of Table to be used as recovering dummy table;
  ** rstowner: Owner of Table to store the recovered data;
  ** rsttable: Name of Table to store the recovered data;
  ** srcdir: Directory of the Data File to be recovered;
  ** srcfile: Name of the Data File to be recovered;
  ** recdir: Directory of Data File that rectable is stored;
  ** recfile: Name of Data File that rectable is stored;
  ** copydir: Directory of Copy of Data File that rectable is stored;
  ** coryfile: Name of Copy of Data File that rectable is stored;
  ** blksz: Block size of the Tablespace Storing the Table to be recovered;
  ** selflink: database link refer to instance self connect to dba account;
  ************************************************************************/
  procedure recover_table(tgtowner varchar2,
                          tgttable varchar2,
                          recowner varchar2,
                          rectab varchar2,
                          rstowner varchar2,
                          rsttab varchar2,
                          srcdir varchar2,
                          recdir varchar2,
                          recfile varchar2,
                          copydir varchar2,
                          copyfile varchar2,
                          blksz number default 8192,
                          fillblks number default 5,
                          selflink varchar2 default '');
  /************************************************************************
  ** Prepare the data files to be use during recovering;
  **
  ** tgtowner: Owner of Target Table to be recovered;
  ** tgttable: Name of Target Table to be recovered;
  ** datapath: Absolute path of Data Files;
  ** datadir: Directory to be created referring to datapath;
  ** rects: Tablespace to store the recovering dummy table;
  ** recfile: Name of Data File to store the recovering dummy table;
  ** rstts: Tablespace to store table storing the recovered data;
  ** rstfile: Name of Data File to store restoring table;
  ** blksz: Block size of the Tablespace Storing the Table to be recovered;
  ** rectsblks: block number of recovery tablespace
  ** rectsblks: block number of restore tablespace
  ************************************************************************/
  procedure prepare_files(tgtowner varchar2,
                          tgttable varchar2,
                          datapath in varchar2,
                          datadir in out varchar2,
                          rects out varchar2,
                          recfile out varchar2,
                          rstts out varchar2,
                          rstfile out varchar2,
                          blksz out varchar2,
                          rectsblks number default 16,
                          rsttsblks number default 2560);
  /************************************************************************
  ** Clean up existing Recover and Restore Tablespace. Drop tables in the tablespaces
  **
  ** rects: Recover tablespace name
  ** rects: Restore tablespace name, default NULL, will not do cleaning up;
  ************************************************************************/
  procedure clean_up_ts(rects varchar2,
                        rstts varchar2 default null);
  /************************************************************************
  ** Fill Blocks of Recovering Table, to format the blocks;
  **
  ** tgtowner: Owner of Target Table to be recovered;
  ** tgttable: Name of Target Table to be recovered;
  ** datadir: Directory to be created referring to datapath;
  ** rects: Tablespace to store the recovering dummy table;
  ** recfile: Name of Data File to store the recovering dummy table;
  ** rstts: Tablespace to store table storing the recovered data;
  ** blks: Number blocks in Initial Extent of the recovering dummy table;
  ** recowner: Owner of Table to be used as recovering dummy table;
  ** rstowner: Owner of Table to store the recovered data;
  ** rectable: Name of Table to be used as recovering dummy table;
  ** rsttable: Name of Table to store the recovered data;
  ** coryfile: Name of Copy of Data File that rectable is stored;
  ************************************************************************/
  procedure fill_blocks(tgtowner varchar2,
                        tgttable varchar2,
                        datadir varchar2,
                        rects varchar2,
                        recfile varchar2,
                        rstts varchar2,
                        blks number default 8,
                        recowner varchar2 default user,
                        rstowner varchar2 default user,
                        rectab in out varchar2,
                        rsttab in out varchar2,
                        copyfile out varchar2);
  /************************************************************************
  ** Testing recovering procedure, involved initializing tablespaces and data files
  **
  ** tgtowner: Owner of Target Table to be recovered;
  ** tgttable: Name of Target Table to be recovered;
  ** datapath: Absolute path of Data Files;
  ** fbks: block number to be filled in recovery table;
  ************************************************************************/
  procedure test_rec1( tow varchar2 default 'SYS',
                       ttb varchar2 default 'T_CHAIN',
                       fbks number default 1,
                       datapath varchar2 default 'D:\oracle\product\10.2.0\oradata\EDGAR\DATAFILE\');
  /************************************************************************
  ** Testing recovering procedure, without initializing tablespaces and data files
  **
  ** tgtowner: Owner of Target Table to be recovered;
  ** tgttable: Name of Target Table to be recovered;
  ** fbks: block number to be filled in recovery table;
  ************************************************************************/
  procedure test_rec2( tow varchar2 default 'SYS',
                       ttb varchar2 default 'T_CHAIN',
                       fbks number default 1);
  /************************************************************************
  ** Set Initial parameters
  **
  ** tracing: trace the process for debug;
  ** logging: show logging information;
  ** repobjid: replace the data object id wiht the recover table data object id;
  ************************************************************************/
  procedure init_set( tracing boolean default true,
                      logging boolean default true,
                      repobjid boolean default true);
end pg_lan_recover_tab_data;
/
create or replace package body pg_lan_recover_tab_data is
  ---------------------------------------------------------------------------
 
  ---------------------------------------------------------------------------
  s_tracing       boolean:= true;
  s_logging       boolean:= true;
  s_repobjid      boolean:= false;
  procedure init_set (tracing boolean default true,
                      logging boolean default true,
                      repobjid boolean default true)
  as
  begin
    s_tracing := tracing;
    s_logging := logging;
    s_repobjid := repobjid;
  end;
  procedure trace (msg varchar2)
  as
  begin
    if s_tracing then
      dbms_output.put_line(to_char(sysdate, 'HH24:MI:SS')||': '||msg);
    end if;
  end;
  procedure log (msg varchar2)
  as
  begin
    if s_logging then
      dbms_output.put_line(to_char(sysdate, 'HH24:MI:SS')||': '||msg);
    end if;
  end;
  function d2r (dig varchar2,
                len number default 0)
  return raw
  is
  begin
    --trace('[d2r] hextoraw(lpad(trim(to_char('||dig||', ''XXXXXXXX'')),'||len||',''0''))');
    return hextoraw(lpad(trim(to_char(dig, 'XXXXXXXX')),len,'0'));
  end;
  procedure copy_file(srcdir varchar2,
                      srcfile varchar2,
                      dstdir varchar2 default null,
                      dstfile varchar2 default null)
  as
    bfr    utl_file.file_type;
    bfw    utl_file.file_type;
    -- vrw    raw(8192);
    frw    raw(8192);
    bsz    number := 8192;
    --p_srcdir varchar2(255) := upper(srcdir);
    --p_srcfile varchar2(255) := upper(srcfile);
    --p_dstdir varchar2(255) := upper(dstdir);
    --p_dstfile varchar2(255) := upper(dstfile);
    p_srcdir varchar2(255) := srcdir;
    p_srcfile varchar2(255) := srcfile;
    p_dstdir varchar2(255) := dstdir;
    p_dstfile varchar2(255) := dstfile;
  begin
    if dstdir is null then
      p_dstdir := p_srcdir;
    end if;
    if p_dstfile is null then
      p_dstfile := p_srcfile||'$';
    end if;
    trace('[copy_file] begin copy file: '||srcdir||'\'||srcfile||' => '||dstdir||'\'||dstfile);
    bfr := utl_file.fopen(p_srcdir, p_srcfile, 'RB');
    bfw := utl_file.fopen(p_dstdir, p_dstfile, 'WB');
    while true loop
    begin
      utl_file.get_raw(bfr, frw, bsz);
      exit when frw is null;
      utl_file.put_raw(bfw, frw);
      utl_file.fflush(bfw);
      exception when others then
        exit;
    end;
    end loop;
    utl_file.fclose(bfw);
    utl_file.fclose(bfr);
    trace('[copy_file] completed.');
  end;
  procedure replace_segmeta_in_file(srcdir varchar2,
                                    srcfile varchar2,
                                    dstdir varchar2,
                                    dstfile varchar2,
                                    tgtobjid number,
                                    newobjid number,
                                    dtail raw,
                                    addpos number,
                                    addinfo raw,
                                    blksz number default 8192,
                                    endianess number default 1)
  as
    bfr    utl_file.file_type;
    bfw    utl_file.file_type;
    hsz    number := 24;
    objr   raw(4);
    objn   number;
    dhead  raw(32);
    dbody  raw(32767);
    nbody  raw(32767);
    p_srcdir varchar2(255) := srcdir;
    p_srcfile varchar2(255) := srcfile;
    p_dstdir varchar2(255) := dstdir;
    p_dstfile varchar2(255) := dstfile;
  begin
    if p_dstdir is null then
      p_dstdir := p_srcdir;
    end if;
    trace('[replace_objid_in_file] replace object id in '||srcdir||'\'||srcfile||' ['||tgtobjid||' => '||newobjid||']');
    bfr := utl_file.fopen(p_srcdir, p_srcfile, 'RB');
    bfw := utl_file.fopen(p_dstdir, p_dstfile, 'WB');
    while true loop
    begin
      nbody := '';
      utl_file.get_raw(bfr, dhead, hsz);
      exit when dhead is null;
      utl_file.get_raw(bfr, dbody, blksz-hsz);
      --objr := hextoraw(substrb(rawtohex(dbody), 1, 8));
      objr := utl_raw.substr(dbody, 1, 4);
      if endianess > 0 then
        objn := to_number(rawtohex(utl_raw.reverse(objr)), 'XXXXXXXX');
      else
        objn := to_number(rawtohex(objr), 'XXXXXXXX');
      end if;
      -- replace data object id with the recover object id
      --if objn = tgtobjid and substrb(rawtohex(dhead), 1, 2) = '06' then
      if objn = tgtobjid then
        if addpos <= hsz then
          --utl_file.put_raw(bfw, utl_raw.concat(utl_raw.substr(dhead, 1, addpos-1), addinfo, utl_raw.substr(dhead, addpos+utl_raw.length(addinfo))));
          nbody := utl_raw.concat(nbody, utl_raw.substr(dhead, 1, addpos-1), addinfo, utl_raw.substr(dhead, addpos+utl_raw.length(addinfo)));
        else
          --utl_file.put_raw(bfw, dhead);
          nbody := utl_raw.concat(nbody, dhead);
        end if;
        --utl_file.put_raw(bfw, utl_raw.concat(utl_raw.substr(dhead, 1, 8), addinfo, utl_raw.substr(dhead, 9+utl_raw.length(addinfo))));
        --nbody := utl_raw.concat(nbody, utl_raw.substr(dhead, 1, 8), addinfo, utl_raw.substr(dhead, 9+utl_raw.length(addinfo)));
        --trace('[replace_objid_in_file] old id in raw: '||rawtohex(objr));
        if endianess > 0 then
          --trace('[replace_objid_in_file] new id in raw: '||utl_raw.reverse(d2r(newobjid, 8)));
          --utl_file.put_raw(bfw, utl_raw.reverse(d2r(newobjid, 8)));
          nbody := utl_raw.concat(nbody, utl_raw.reverse(d2r(newobjid, 8)));
        else
          --trace('[replace_objid_in_file] new id in raw: '||(d2r(newobjid, 8)));
          --utl_file.put_raw(bfw, d2r(newobjid, 8));
          nbody := utl_raw.concat(nbody, d2r(newobjid, 8));
        end if;
        -- skip objid
        if addpos > hsz+5 and addinfo is not null then
          trace('[replace_objid_in_file] old body len: '||utl_raw.length(dbody)||' new = 4 + '||utl_raw.length(utl_raw.substr(dbody, 5, addpos-hsz-5))||' + '||utl_raw.length(addinfo)||' + '||utl_raw.length(utl_raw.substr(dbody, addpos-hsz-4+utl_raw.length(addinfo), blksz-(addpos-1)-utl_raw.length(dtail)-utl_raw.length(addinfo)))||' + 4');
          --utl_file.put_raw(bfw, utl_raw.concat(utl_raw.substr(dbody, 5, addpos-hsz-5), addinfo, utl_raw.substr(dbody, addpos-hsz, blksz-(addpos-1)-utl_raw.length(dtail)-utl_raw.length(addinfo))));
          nbody := utl_raw.concat(nbody, utl_raw.substr(dbody, 5, addpos-hsz-5), addinfo, utl_raw.substr(dbody, addpos-hsz-4+utl_raw.length(addinfo), blksz-(addpos-1)-utl_raw.length(dtail)-utl_raw.length(addinfo)));
          --trace('[replace_objid_in_file] new body len: '||utl_raw.length(nbody));
        elsif addpos = hsz+5 and addinfo is not null then
          --utl_file.put_raw(bfw, utl_raw.concat(addinfo, utl_raw.substr(dbody, addpos-hsz, blksz-(addpos-1)-utl_raw.length(dtail)-utl_raw.length(addinfo))));
          nbody := utl_raw.concat(nbody, addinfo, utl_raw.substr(dbody, addpos-hsz, blksz-(addpos-1)-utl_raw.length(dtail)-utl_raw.length(addinfo)));
        else
          --utl_file.put_raw(bfw, utl_raw.substr(dbody, 5, blksz-hsz-4-utl_raw.length(dtail)));
          nbody := utl_raw.concat(nbody, utl_raw.substr(dbody, 5, blksz-hsz-4-utl_raw.length(dtail)));
        end if;
        --trace('[replace_objid_in_file] tail in raw: '||dtail||'('||utl_raw.length(dtail)||')');
        --utl_file.put_raw(bfw, dtail);
        nbody := utl_raw.concat(nbody, dtail);
        trace('[replace_objid_in_file] new body length: '||utl_raw.length(nbody));
      else
        --utl_file.put_raw(bfw, dhead);
        --utl_file.put_raw(bfw, dbody);
        nbody := utl_raw.concat(nbody, dhead, dbody);
      end if;
      --if utl_raw.length(nbody) != blksz then
      --  trace('[replace_objid_in_file] new body length: '||utl_raw.length(nbody));
      --end if;
      utl_file.put_raw(bfw, nbody);
      utl_file.fflush(bfw);
      exception
        when no_data_found then
          exit;
        when others then
          trace('[replace_objid_in_file] '||SQLERRM);
          trace('[replace_objid_in_file] '||dbms_utility.format_error_backtrace);
          exit;
    end;
    end loop;
    utl_file.fclose(bfw);
    utl_file.fclose(bfr);
    trace('[replace_objid_in_file] completed.');
  end;
  function gen_table_name(tgttable varchar2,
                          plus     varchar2 default '',
                          genowner varchar2 default user)
  return varchar2
  as
    gentab varchar2(30);
  begin
    select upper(tgttable||plus||surfix) into gentab from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_tables where owner = genowner and table_name = upper(tgttable||plus||surfix)) order by surfix nulls first) where rownum<=1;
    return gentab;
  end;
  function gen_file_name( tgtfile varchar2,
                          plus    varchar2 default '')
  return varchar2
  as
    genfile varchar2(30);
  begin
    select tgtfile||plus||surfix||'.DAT' into genfile from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_data_files where file_name like '%\'||tgtfile||plus||surfix||'.DAT') order by surfix nulls first) where rownum<=1;
    return genfile;
  end;
  function gen_ts_name( tgtts  varchar2,
                        plus   varchar2 default '')
  return varchar2
  as
    gents varchar2(30);
  begin
    select tgtts||plus||surfix into gents from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_tablespaces where tablespace_name = tgtts||plus||surfix) order by surfix nulls first) where rownum<=1;
    return gents;
  end;
  function get_cols_no_lob( recowner varchar2,
                            rectab varchar2)
  return varchar2
  as
    cols        varchar2(32767);
    colno       number := 0;
  begin
    cols := '';
    for col_rec in (select column_name, data_type, nullable from dba_tab_cols where owner = recowner and table_name = rectab) loop
      if col_rec.data_type NOT LIKE '%LOB' then
        if colno > 0 then
          cols := cols||',';
        end if;
        cols := cols||col_rec.column_name;
        colno := colno + 1;
      end if;
    end loop;
    return cols;
  end;
  function restore_table_row_no_lob(recowner varchar2,
                                    rectab varchar2,
                                    rstowner varchar2,
                                    rsttab varchar2,
                                    cols varchar2,
                                    rid rowid)
  return number
  as
    recnum      number := 0;
  begin
    begin
      execute immediate 'insert /*+*/ into '||rstowner||'.'||rsttab||'('||cols||') select '||cols||' from '||recowner||'.'||rectab||' where rowid = :rid' using rid;
      recnum := recnum + SQL%ROWCOUNT;
    exception when others then
      trace('[restore_table_row_no_lob] '||SQLERRM);
      trace('[restore_table_row_no_lob] '||dbms_utility.format_error_backtrace);
      null;
    end;
    return recnum;
  end;
  function restore_table_in_rows( recowner varchar2,
                                  rectab varchar2,
                                  rstowner varchar2,
                                  rsttab varchar2)
  return number
  as
    recnum      number := 0;
    blk_cur     r_cursor;
    objid       number;
    fid         number;
    blkno       number;
    rnum        number;
    gnum        number;
    cols        varchar2(32767);
  begin
    begin
      --trace('[restore_table_in_rows] '||'select dbms_rowid.rowid_object(rowid) objid, dbms_rowid.rowid_relative_fno(rowid) fid, dbms_rowid.rowid_block_number(rowid) blkno, count(1) rnum from '||recowner||'.'||rectab||' group by dbms_rowid.rowid_object(rowid), dbms_rowid.rowid_relative_fno(rowid), dbms_rowid.rowid_block_number(rowid)');
      open blk_cur for 'select dbms_rowid.rowid_object(rowid) objid, dbms_rowid.rowid_relative_fno(rowid) fid, dbms_rowid.rowid_block_number(rowid) blkno, count(1) rnum from '||recowner||'.'||rectab||' group by dbms_rowid.rowid_object(rowid), dbms_rowid.rowid_relative_fno(rowid), dbms_rowid.rowid_block_number(rowid)';
      loop
        fetch blk_cur into objid, fid, blkno, rnum;
        exit when blk_cur%NOTFOUND;
        trace('[restore_table_in_rows] expected rows: '||rnum);
        gnum := 0;
        --trace('[restore_table_in_rows] block: '||blkno);
        for i in 1..rnum loop
        begin
          --trace('[restore_table_in_rows] row: '||i);
          --execute immediate 'insert /*+*/into '||rstowner||'.'||rsttab||' select * from '||recowner||'.'||rectab||' where rowid = dbms_rowid.rowid_create(1, :objid, :fid, :blkno, :i)' using objid, fid, blkno, i-1;
          execute immediate 'insert /*+*/into '||rstowner||'.'||rsttab||' select * from '||recowner||'.'||rectab||' where dbms_rowid.rowid_relative_fno(ROWID)=:fid and dbms_rowid.rowid_block_number(ROWID)=:blkno and dbms_rowid.rowid_row_number(ROWID)=:i' using fid, blkno, i-1;
          recnum := recnum + SQL%ROWCOUNT;
          gnum := gnum + SQL%ROWCOUNT;
        exception when others then
          if sqlcode = -22922 then
            -- trace('[restore_table_in_rows] Warning: Unrecoverable Lob found!');
            if cols is null then
              cols := get_cols_no_lob(recowner, rectab);
            end if;
            recnum := recnum + restore_table_row_no_lob(recowner, rectab, rstowner, rsttab, cols, dbms_rowid.rowid_create(1, objid, fid, blkno, i-1));
          else
            trace('[restore_table_in_rows] '||SQLERRM);
            trace('[restore_table_in_rows] '||dbms_utility.format_error_backtrace);
          end if;
          null;
        end;
        end loop;
        if gnum != rnum then
          log('Warning: '||(rnum-gnum)||' records lost!');
        end if;
      end loop;
      exception when others then
        trace('[restore_table_in_rows] '||sqlerrm);
        trace('[restore_table_in_rows] '||dbms_utility.format_error_backtrace);
        null;
    end;
    return recnum;
  end;
  function restore_table_in_rows_remote(recowner varchar2,
                                        rectab varchar2,
                                        rstowner varchar2,
                                        rsttab varchar2,
                                        dblink varchar2)
  return number
  as
    recnum      number := 0;
    blk_cur     r_cursor;
    objid       number;
    fid         number;
    blkno       number;
    rnum        number;
    cols        varchar2(32767);
  begin
    begin
      --rollback;
      open blk_cur for 'select dbms_rowid.rowid_object(rowid) objid, dbms_rowid.rowid_relative_fno(rowid) fid, dbms_rowid.rowid_block_number(rowid) blkno, count(1) rnum from '||recowner||'.'||rectab||' group by dbms_rowid.rowid_object(rowid), dbms_rowid.rowid_relative_fno(rowid), dbms_rowid.rowid_block_number(rowid)';
      loop
        fetch blk_cur into objid, fid, blkno, rnum;
        exit when blk_cur%NOTFOUND;
        trace('[restore_table_in_rows_remote] expected rows: '||rnum);
        for i in 1..rnum loop
        begin
          --execute immediate 'insert /*+no_append*/into '||rstowner||'.'||rsttab||' select * from '||recowner||'.'||rectab||'@'||dblink||' where rowid = dbms_rowid.rowid_create(1, :objid, :fid, :blkno, :i)' using objid, fid, blkno, i-1;
          execute immediate 'insert /*+*/into '||rstowner||'.'||rsttab||' select * from '||recowner||'.'||rectab||'@'||dblink||' where dbms_rowid.rowid_relative_fno(ROWID)=:fid and dbms_rowid.rowid_block_number(ROWID)=:blkno and dbms_rowid.rowid_row_number(ROWID)=:i' using fid, blkno, i-1;
          recnum := recnum + SQL%ROWCOUNT;
          --commit;
        exception when others then
          if sqlcode = -22922 then
            if cols is null then
              cols := get_cols_no_lob(recowner, rectab);
            end if;
            recnum := recnum + restore_table_row_no_lob(recowner, rectab, rstowner, rsttab, cols, dbms_rowid.rowid_create(1, objid, fid, blkno, i-1));
          else
            trace('[restore_table_in_rows_remote] '||SQLERRM);
            trace('[restore_table_in_rows_remote] '||dbms_utility.format_error_backtrace);
            --commit;
          end if;
          null;
        end;
        end loop;
      end loop;
    end;
    return recnum;
  end;
  function restore_table_ctas(recowner varchar2,
                              rectab varchar2,
                              rstowner varchar2,
                              rsttab varchar2)
  return number
  as
    recnum      number := 0;
    tmptab      varchar2(30);
  begin
    tmptab := gen_table_name(rsttab, '', rstowner);
    begin
      execute immediate 'create table '||rstowner||'.'||tmptab||' as select /*+full(t)*/* from '||recowner||'.'||rectab||' t';
      execute immediate 'insert /*+append*/ into '||rstowner||'.'||rsttab||' select /*+full(t)*/* from '||recowner||'.'||tmptab||' t';
      recnum := SQL%ROWCOUNT;
      execute immediate 'drop table '||rstowner||'.'||tmptab;
      exception when others then
        --trace('[restore_table_ctas] '||SQLERRM);
        --trace('[restore_table_ctas] '||dbms_utility.format_error_backtrace);
        null;
    end;
    return recnum;
  end;
  function restore_table_no_lob(recowner varchar2,
                                rectab varchar2,
                                rstowner varchar2,
                                rsttab varchar2)
  return number
  as
    recnum      number := 0;
    cols        varchar2(32767);
  begin
    cols := get_cols_no_lob(recowner, rectab);
    begin
      --execute immediate 'alter system flush buffer_cache';
      execute immediate 'insert /*+append*/ into '||rstowner||'.'||rsttab||'('||cols||') select /*+full(t)*/'||cols||' from '||recowner||'.'||rectab||' t';
      recnum := recnum + SQL%ROWCOUNT;
    exception when others then
      --raise;
      if sqlcode = -22922 then
        null;
      else
        recnum := recnum + restore_table_in_rows(recowner, rectab, rstowner, rsttab);
      end if;
    end;
    trace('[restore_table_no_lob] '||recnum||' records recovered');
    return recnum;
  end;
  function restore_table( recowner varchar2,
                          rectab varchar2,
                          rstowner varchar2,
                          rsttab varchar2,
                          selflink varchar2 default '')
  return number
  as
    recnum      number := 0;
    expnum      number := 0;
  begin
    begin
      trace('[restore_table] Trying to restore data to '||rstowner||'.'||rsttab);
      execute immediate 'alter system flush buffer_cache';
      if s_tracing then
        execute immediate 'select /*+full(t)*/count(*) from '||recowner||'.'||rectab||' t' into expnum;
        trace('[restore_table] Expected Records in this round: '||expnum);
      end if;
      execute immediate 'insert /*+append*/ into '||rstowner||'.'||rsttab||' select /*+full(t)*/* from '||recowner||'.'||rectab||' t';
      recnum := recnum + SQL%ROWCOUNT;
      if s_tracing and expnum != SQL%ROWCOUNT then
        trace('[restore_table] '||(expnum-SQL%ROWCOUNT)||' records lost!');
        return -1; -- for test
      end if;
    exception when others then
      --raise;
      if sqlcode = -22922 then
        log('Warning: Unrecoverable Lob found!');
        recnum := recnum + restore_table_in_rows_remote(recowner, rectab, rstowner, rsttab, selflink);
        --recnum := recnum + restore_table_no_lob(recowner, rectab, rstowner, rsttab);
      else
        trace(SQLERRM);
        trace('[restore_table] '||dbms_utility.format_error_backtrace);
        --recnum := recnum + restore_table_in_rows_remote(recowner, rectab, rstowner, rsttab, selflink);
        --return -1; -- test
        recnum := recnum + restore_table_in_rows(recowner, rectab, rstowner, rsttab);
      end if;
    end;
    execute immediate 'alter system flush buffer_cache';
    trace('[restore_table] '||recnum||' records recovered');
    return recnum;
  end;
  procedure recover_table(oriobjid number,
                          recowner varchar2,
                          rectab varchar2,
                          rstowner varchar2,
                          rsttab varchar2,
                          srcdir varchar2,
                          srcfile varchar2,
                          recdir varchar2,
                          recfile varchar2,
                          copydir varchar2,
                          copyfile varchar2,
                          blksz number default 8192,
                          fillblks number default 5,
                          selflink varchar2 default '',
                          endianess number default 1)
  as
    -- blk    blob;
    --vrw    raw(32767);
    frw    raw(32767);
    tsz    number := 4;
    hsz    number := 28;
    objr   raw(4);
    objn   number;
    dtail  raw(4);
    dhead  raw(32);
    dbody  raw(32767);
    --bfr    bfile;
    bfo    utl_file.file_type;
    bfr    utl_file.file_type;
    bfw    utl_file.file_type;
    fillednum number := 0;
    dummyheader number;
    dummyblks   number;
    blkstofill  number := fillblks;
    recnum      number := 0;
    rstnum      number := 0;
    i           number := 0;
    j           number := 0;
    truncblks   number := 0;
  begin
    execute immediate 'truncate table '||rstowner||'.'||rsttab;
    execute immediate 'alter system set db_block_checking=false scope=memory';
    execute immediate 'alter system set db_block_checksum=false scope=memory';
    execute immediate 'alter system set "_db_block_check_objtyp"=false scope=memory';
    execute immediate 'alter session set events ''10231 trace name context forever, level 10''';
    execute immediate 'alter session set events ''10233 trace name context forever, level 10''';
    select header_block+1, blocks-3 into dummyheader, dummyblks from dba_segments where owner = recowner and segment_name = rectab;
    if blkstofill > dummyblks then
      blkstofill := dummyblks;
    end if;
    bfo := utl_file.fopen(srcdir, srcfile, 'RB');
    --utl_file.get_raw(bfo, dbody, blksz-hsz-tsz);
    --utl_file.get_raw(bfo, dtail, tsz);
    bfr := utl_file.fopen(copydir, copyfile, 'RB');
    bfw := utl_file.fopen(recdir, recfile, 'WB');
    -- reach to the transaction blocks to be filled
    i := 0;
    while true loop
    begin
      utl_file.get_raw(bfr, frw, blksz);
      utl_file.put_raw(bfw, frw);
      i := i+1;
      exit when i=dummyheader+fillednum;
      exception when others then
        --raise;
        --trace('[recover_table] block NO.: '||i);
        exit;
    end;
    end loop;
    -- go through the data file of truncated table
    while true loop
    begin
      --trace('[recover_table] '||j);
      j := j+1;
      --objr := substrb(rawtohex(dhead), 49, 8);
      utl_file.get_raw(bfo, dhead, hsz);
      if hsz <= 24 then
        utl_file.get_raw(bfo, dbody, blksz-tsz-hsz);
        --objr := substrb(rawtohex(dbody), 49-hsz*2, 8);
        objr := utl_raw.substr(dbody, 25-hsz, 4);
      else
        --objr := substrb(rawtohex(dhead), 49, 8);
        objr := utl_raw.substr(dhead, 25, 4);
      end if;
      if endianess > 0 then
        --objn := to_number(utl_raw.reverse(hextoraw(objr)), 'XXXXXXXX');
        objn := to_number(rawtohex(utl_raw.reverse(objr)), 'XXXXXXXX');
      else
        --objn := to_number(hextoraw(objr), 'XXXXXXXX');
        objn := to_number(rawtohex(objr), 'XXXXXXXX');
      end if;
      -- check if block belongs to truncated table
      if objn != oriobjid or substrb(rawtohex(dhead), 1, 2) != '06' then
        if hsz > 24 then
          utl_file.get_raw(bfo, dbody, blksz-hsz);
        else
          utl_file.get_raw(bfo, dtail, tsz);
        end if;
      else
        --trace('[recover_table] Find it.');
        truncblks := truncblks + 1;
        if hsz > 24 then
          utl_file.get_raw(bfo, dbody, blksz-hsz-tsz);
        end if;
        utl_file.get_raw(bfo, dtail, tsz);
        if not utl_file.is_open(bfr) then
          bfr := utl_file.fopen(copydir, copyfile, 'RB');
        end if;
        if not utl_file.is_open(bfw) then
          bfw := utl_file.fopen(recdir, recfile, 'WB');
        end if;
        -- filling the trans block
        utl_file.get_raw(bfr, dhead, hsz);
        utl_file.put_raw(bfw, dhead); -- put original header
        utl_file.put_raw(bfw, dbody); -- replace body
        utl_file.get_raw(bfr, dbody, blksz-hsz-tsz); -- forward pointer in original file copy
        utl_file.get_raw(bfr, dtail, tsz); -- get original tail
        utl_file.put_raw(bfw, dtail); -- put original tail
        fillednum := fillednum+1;
        i := i+1;
        -- no trans data block left, copy recovered data to backup table and fill the left blocks
        if fillednum >= blkstofill then
        --if fillednum+blkstofill-1 >= dummyblks then
        begin
          while true loop
          begin
            utl_file.get_raw(bfr, frw, blksz);
            utl_file.put_raw(bfw, frw);
            i := i+1;
            exception when others then
              if utl_file.is_open(bfr) then
                utl_file.fclose(bfr);
              end if;
              if utl_file.is_open(bfw) then
                utl_file.fclose(bfw);
              end if;
              exit;
          end;
          end loop;
          rstnum := restore_table(recowner, rectab, rstowner, rsttab, selflink);
          -- for test
          exit when rstnum < 0;
          recnum := recnum+rstnum;
          fillednum := 0;
          commit;
          bfr := utl_file.fopen(copydir, copyfile, 'RB');
          bfw := utl_file.fopen(recdir, recfile, 'WB');
          -- go to the transaction blocks again
          i := 0;
          while true loop
          begin
            utl_file.get_raw(bfr, frw, blksz);
            utl_file.put_raw(bfw, frw);
            i := i+1;
            exit when i=dummyheader+fillednum;
            exception when others then
              --raise;
              --trace('[recover_table] block NO.: '||i);
              exit;
          end;
          end loop;
          utl_file.fflush(bfw);
          exception when others then
            trace('[recover_table 2-1] '||sqlerrm);
            trace('[recover_table 2-1] '||dbms_utility.format_error_backtrace);
            null;
          end;
        end if;
      end if;
      exception
        when no_data_found then
          exit;
        when others then
          trace('[recover_table 2-2] '||sqlerrm);
          trace('[recover_table 2-2] '||dbms_utility.format_error_backtrace);
          exit;
    end;
    end loop;
    -- last blocks not full filled dummy table
    --if fillednum+blkstofill-1 < dummyblks then
    if fillednum < blkstofill and rstnum>=0 then
    begin
      while true loop
      begin
        utl_file.get_raw(bfr, frw, blksz);
        utl_file.put_raw(bfw, frw);
        i := i+1;
        exception when others then
          if utl_file.is_open(bfr) then
            utl_file.fclose(bfr);
          end if;
          if utl_file.is_open(bfw) then
            utl_file.fclose(bfw);
          end if;
          exit;
      end;
      end loop;
      recnum := recnum+restore_table(recowner, rectab, rstowner, rsttab, selflink);
      --fillednum := 0;
      commit;
    end;
    end if;
    if utl_file.is_open(bfr) then
      utl_file.fclose(bfr);
    end if;
    if utl_file.is_open(bfw) then
      utl_file.fclose(bfw);
    end if;
    if utl_file.is_open(bfo) then
      utl_file.fclose(bfo);
    end if;
    utl_file.fclose_all();
    execute immediate 'alter session set events ''10233 trace name context off''';
    execute immediate 'alter session set events ''10231 trace name context off''';
    execute immediate 'alter system set "_db_block_check_objtyp"=true scope=memory';
    execute immediate 'alter system set db_block_checksum=true scope=memory';
    execute immediate 'alter system set db_block_checking=true scope=memory';
    log(truncblks||' truncated data blocks found. ');
    log(recnum||' records recovered in backup table '||rstowner||'.'||rsttab);
  end;
  procedure get_seg_meta( segowner varchar2,
                          segname varchar2,
                          srcdir varchar2,
                          dtail out raw,
                          addinfo out raw,
                          blksz number default 8192)
  as
    frw    raw(32767);
    firstblk  number;
    hdfile varchar2(255);
    bfo    utl_file.file_type;
    i      number := 0;
  begin
    select header_block+1 into firstblk from dba_segments where owner = segowner and segment_name = segname;
    select substr(file_name,decode(instr(d.file_name, '\', -1), 0, instr(file_name, '/', -1), instr(file_name, '\', -1))+1) into hdfile from dba_data_files d, dba_segments s where s.header_file = d.file_id and s.owner = segowner and s.segment_name = segname;
    bfo := utl_file.fopen(srcdir, hdfile, 'RB');
    -- reach to the truncated data blocks
    i := 0;
    while true loop
    begin
      utl_file.get_raw(bfo, frw, blksz);
      i := i+1;
      exit when i = firstblk;
      exception when others then
        exit;
    end;
    end loop;
    utl_file.get_raw(bfo, frw, blksz);
    dtail := utl_raw.substr(frw, blksz-3, 4);
    addinfo := utl_raw.substr(frw, 39, 2);
    utl_file.fclose(bfo);
  end;
  function get_seg_data_id( segowner varchar2,
                            segname varchar2,
                            srcdir varchar2,
                            blksz number default 8192,
                            endianess number default 1)
  return number
  as
    frw    raw(32767);
    hsz    number := 28;
    firstblk  number;
    hdfile varchar2(255);
    bfo    utl_file.file_type;
    i      number := 0;
    objr   raw(4);
    objn   number;
  begin
    select header_block+1 into firstblk from dba_segments where owner = segowner and segment_name = segname;
    select substr(file_name,decode(instr(d.file_name, '\', -1), 0, instr(file_name, '/', -1), instr(file_name, '\', -1))+1) into hdfile from dba_data_files d, dba_segments s where s.header_file = d.file_id and s.owner = segowner and s.segment_name = segname;
    bfo := utl_file.fopen(srcdir, hdfile, 'RB');
    -- reach to the truncated data blocks
    i := 0;
    while true loop
    begin
      utl_file.get_raw(bfo, frw, blksz);
      i := i+1;
      exit when i = firstblk;
      exception when others then
        exit;
    end;
    end loop;
    utl_file.get_raw(bfo, frw, hsz);
    objr := utl_raw.substr(frw, 25, 4);
    if endianess > 0 then
      objn := to_number(rawtohex(utl_raw.reverse(objr)), 'XXXXXXXX');
    else
      objn := to_number(rawtohex(objr), 'XXXXXXXX');
    end if;
    utl_file.fclose(bfo);
    return objn;
  end;
  procedure recover_table(tgtowner varchar2,
                          tgttable varchar2,
                          recowner varchar2,
                          rectab varchar2,
                          rstowner varchar2,
                          rsttab varchar2,
                          srcdir varchar2,
                          recdir varchar2,
                          recfile varchar2,
                          copydir varchar2,
                          copyfile varchar2,
                          blksz number default 8192,
                          fillblks number default 5,
                          selflink varchar2 default '')
  as
    tgtobjid    number;
    recobjid    number;
    endianess   number;
    tmpcopyf    varchar2(256);
    tsname      varchar2(30);
    readprop    varchar2(30);
    dtail       raw(4);
    addinfo     raw(32);
  begin
    select instr(platform_name, 'Windows') into endianess from v$database where rownum<=1;
    select data_object_id into recobjid from dba_objects where owner = recowner and object_name = rectab and object_type='TABLE' and rownum<=1;
    log('begin to recover table '||tgtowner||'.'||tgttable);
    tgtobjid := get_seg_data_id(tgtowner, tgttable, srcdir, blksz, endianess);
    if s_repobjid then
      get_seg_meta(recowner, rectab, srcdir, dtail, addinfo, blksz);
      select tablespace_name into tsname from dba_tables where owner = tgtowner and table_name = tgttable and rownum<=1;
      select STATUS into readprop from dba_tablespaces where tablespace_name = tsname;
      if readprop != 'READ ONLY' then
        execute immediate 'alter tablespace '||tsname||' read only';
        execute immediate 'alter system flush buffer_cache';
      end if;
      for file_rec in (select substr(file_name,decode(instr(d.file_name, '\', -1), 0, instr(file_name, '/', -1), instr(file_name, '\', -1))+1) as filename from dba_data_files d, dba_tables t where d.tablespace_name = t.tablespace_name and t.owner = tgtowner and t.table_name = tgttable) loop
      begin
        tmpcopyf := gen_file_name(file_rec.filename, '$');
        copy_file(srcdir, file_rec.filename, srcdir, tmpcopyf);
        --replace_segmeta_in_file(srcdir, tmpcopyf, srcdir, file_rec.filename, tgtobjid, recobjid, dtail, 39, addinfo, blksz, endianess);
        replace_segmeta_in_file(srcdir, tmpcopyf, srcdir, file_rec.filename, tgtobjid, recobjid, dtail, 39, '', blksz, endianess);
        recover_table(recobjid, recowner, rectab, rstowner, rsttab, srcdir, file_rec.filename, recdir, recfile, copydir, copyfile, blksz, fillblks, selflink, endianess);
        --recover_table(tgtobjid, recowner, rectab, rstowner, rsttab, srcdir, file_rec.filename, recdir, recfile, copydir, copyfile, blksz, fillblks, selflink, endianess);
        copy_file(srcdir, tmpcopyf, srcdir, file_rec.filename);
        utl_file.fremove(srcdir, tmpcopyf);
        trace('[recover_table 1] '||tmpcopyf||' removed.');
      exception when others then
        trace('[recover_table 1] '||sqlerrm);
        trace('[recover_table 1] '||dbms_utility.format_error_backtrace);
      end;
      end loop;
      if readprop != 'READ ONLY' then
        execute immediate 'alter tablespace '||tsname||' read write';
      end if;
    else
      for file_rec in (select substr(file_name,decode(instr(d.file_name, '\', -1), 0, instr(file_name, '/', -1), instr(file_name, '\', -1))+1) as filename from dba_data_files d, dba_tables t where d.tablespace_name = t.tablespace_name and t.owner = tgtowner and t.table_name = tgttable) loop
      begin
        recover_table(tgtobjid, recowner, rectab, rstowner, rsttab, srcdir, file_rec.filename, recdir, recfile, copydir, copyfile, blksz, fillblks, selflink, endianess);
      exception when others then
        trace('[recover_table 1] '||sqlerrm);
        trace('[recover_table 1] '||dbms_utility.format_error_backtrace);
      end;
      end loop;
    end if;
    log('Recovery completed.');
  end;
  procedure prepare_files(tgtowner varchar2,
                          tgttable varchar2,
                          datapath varchar2,
                          datadir in out varchar2,
                          rects out varchar2,
                          recfile out varchar2,
                          rstts out varchar2,
                          rstfile out varchar2,
                          blksz out varchar2,
                          rectsblks number default 16,
                          rsttsblks number default 2560)
  as
    ext_mgmt   varchar2(30);
    ss_mgmt    varchar2(30);
  begin
    select block_size, extent_management, segment_space_management into blksz, ext_mgmt, ss_mgmt from dba_tablespaces ts, dba_tables t where t.tablespace_name = ts.tablespace_name and t.owner = upper(tgtowner) and t.table_name = upper(tgttable);
    select datadir||surfix into datadir from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_directories where directory_name = datadir||surfix) order by surfix nulls first) where rownum<=1;
    log('Directory Name: '||datadir);
    execute immediate 'create directory '||datadir||' as '''||datapath||'''';
    --select 'FY_REC_DATA'||surfix into rects from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_tablespaces where tablespace_name = 'FY_REC_DATA'||surfix) order by surfix nulls first) where rownum<=1;
    --select 'FY_REC_DATA'||surfix||'.DAT' into recfile from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_data_files where file_name like '%\FY_REC_DATA'||surfix||'.DAT') order by surfix nulls first) where rownum<=1;
    rects := gen_ts_name('FY_REC_DATA','');
    recfile := gen_file_name('FY_REC_DATA','');
    log('Recover Tablespace: '||rects||'; Data File: '||recfile);
    execute immediate 'create tablespace '||rects||' datafile '''||rtrim(datapath, '\')||'\'||recfile||''' size '||to_char(blksz*rectsblks/1024)||'K autoextend off extent management '||ext_mgmt||' SEGMENT SPACE MANAGEMENT '||ss_mgmt;
    select 'FY_RST_DATA'||surfix into rstts from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_tablespaces where tablespace_name = 'FY_REST_DATA'||surfix) order by surfix nulls first) where rownum<=1;
    --select 'FY_RST_DATA'||surfix||'.DAT' into rstfile from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_data_files where file_name like '%\FY_REST_DATA'||surfix||'.DAT') order by surfix nulls first) where rownum<=1;
    rstts := gen_ts_name('FY_RST_DATA','');
    rstfile := gen_file_name('FY_RST_DATA','');
    log('Restore Tablespace: '||rstts||'; Data File: '||rstfile);
    execute immediate 'create tablespace '||rstts||' datafile '''||rtrim(datapath, '\')||'\'||rstfile||''' size '||to_char(blksz*rsttsblks/1024)||'K autoextend on extent management '||ext_mgmt||' SEGMENT SPACE MANAGEMENT '||ss_mgmt;
  end;
  procedure clean_up_ts(rects varchar2,
                        rstts varchar2 default null)
  as
    readprop varchar2(30);
  begin
    select STATUS into readprop from dba_tablespaces where tablespace_name = rects;
    if readprop = 'READ ONLY' then
      execute immediate 'alter tablespace '||rects||' read write';
    end if;
    for tab_rec in (select owner, table_name from dba_tables where tablespace_name = rects) loop
      execute immediate 'drop table '||tab_rec.owner||'.'||tab_rec.table_name;
    end loop;
    if rstts is not null then
      for tab_rec in (select owner, table_name from dba_tables where tablespace_name = rstts) loop
        execute immediate 'drop table '||tab_rec.owner||'.'||tab_rec.table_name;
      end loop;
    end if;
  end;
  procedure fill_blocks(tgtowner varchar2,
                        tgttable varchar2,
                        datadir varchar2,
                        rects varchar2,
                        recfile varchar2,
                        rstts varchar2,
                        blks number default 8,
                        recowner varchar2 default user,
                        rstowner varchar2 default user,
                        rectab in out varchar2,
                        rsttab in out varchar2,
                        copyfile out varchar2)
  as
    blksz  number;
    blkno  number;
    cols   varchar2(32767);
    vals   varchar2(32767);
    colno  number := 0;
  begin
    if rectab is null then
      select block_size into blksz from dba_tablespaces ts, dba_tables t where t.tablespace_name = ts.tablespace_name and t.owner = upper(tgtowner) and t.table_name = upper(tgttable);
      -- select upper(tgttable||'$'||surfix) into rectab from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_tables where owner = recowner and table_name = upper(tgttable||'$'||surfix)) order by surfix nulls first) where rownum<=1;
      rectab := gen_table_name(tgttable, '$', recowner);
      log('Recover Table: '||recowner||'.'||rectab);
      --trace('[fill_blocks] create table '||recowner||'.'||rectab||' tablespace '||rects||' storage(initial '||to_char(blks*blksz/1024)||'K) as select * from '||tgtowner||'.'||tgttable||' where 1=2');
      execute immediate 'create table '||recowner||'.'||rectab||' tablespace '||rects||' storage(initial '||to_char(blks*blksz/1024)||'K) as select * from '||tgtowner||'.'||tgttable||' where 1=2';
    else
      --execute immediate 'truncate table '||recowner||'.'||rectab;
      execute immediate 'delete from '||recowner||'.'||rectab;
      commit;
    end if;
    cols := '';
    vals := '';
    for col_rec in (select column_name, data_type, nullable from dba_tab_cols where owner = recowner and table_name = rectab) loop
      if col_rec.nullable = 'N' then
        execute immediate 'alter table '||recowner||'.'||rectab||' modify '||col_rec.column_name||' null';
      end if;
      if colno < 6 then
        if col_rec.data_type like '%CHAR%' or col_rec.data_type like '%RAW%' then
          if colno > 0 then
            cols := cols||',';
            vals := vals||',';
          end if;
          cols := cols||col_rec.column_name;
          vals := vals||'''A''';
          colno := colno + 1;
       elsif col_rec.data_type like '%NUMBER%' or col_rec.data_type = 'FLOAT' then
          if colno > 0 then
            cols := cols||',';
            vals := vals||',';
          end if;
          cols := cols||col_rec.column_name;
          vals := vals||'0';
          colno := colno + 1;
       elsif col_rec.data_type LIKE '%TIMESTAMP%' or col_rec.data_type = 'DATE' then
          if colno > 0 then
            cols := cols||',';
            vals := vals||',';
          end if;
          cols := cols||col_rec.column_name;
          vals := vals||'sysdate';
          colno := colno + 1;
       end if;
      end if;
    end loop;
    --select upper(tgttable||'$$'||surfix) into rsttab from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_tables where owner = rstowner and table_name = upper(tgttable||'$$'||surfix)) order by surfix nulls first) where rownum<=1;
    if rsttab is null then
      rsttab := gen_table_name(tgttable, '$$', rstowner);
      log('Restore Table: '||rstowner||'.'||rsttab);
      execute immediate 'create table '||rstowner||'.'||rsttab||' tablespace '||rstts||' as select * from '||recowner||'.'||rectab||' where 1=2';
    else
      execute immediate 'truncate table '||rstowner||'.'||rsttab;
    end if;
    --trace('[fill_blocks] insert into '||recowner||'.'||rectab||'('||cols||') values ('||vals||')');
    while true loop
      execute immediate 'insert into '||recowner||'.'||rectab||'('||cols||') values ('||vals||')';
      execute immediate 'select count(unique(dbms_rowid.rowid_block_number( rowid ))) from '||recowner||'.'||rectab into blkno ;
      exit when blkno >= blks-3;
    end loop;
    commit;
    execute immediate 'alter system flush buffer_cache';
    execute immediate 'delete from '||recowner||'.'||rectab;
    commit;
    execute immediate 'alter system flush buffer_cache';
    trace('[fill_blocks] Data Blocks formatted.');
    execute immediate 'alter tablespace '||rects||' read only';
    select 'FY_REC_DATA_COPY'||surfix||'.DAT' into copyfile from (select surfix from (select null surfix from dual union all select level surfix from dual connect by level <= 255) where not exists (select 1 from dba_data_files where file_name like '%FY_REC_DATA_COPY'||surfix||'.DAT') order by surfix nulls first) where rownum<=1;
    copy_file(datadir, recfile, datadir, copyfile);
    log('Copy file of Recover Tablespace: '||copyfile);
  end;
  procedure test_rec1( tow varchar2 default 'SYS',
                       ttb varchar2 default 'T_CHAIN',
                       fbks number default 1,
                       datapath varchar2 default 'D:\oracle\product\10.2.0\oradata\EDGAR\DATAFILE\')
  as
    tgtowner varchar2(30):= upper(tow);
    tgttable varchar2(30):= upper(ttb);
    datadir varchar2(30);
    rects varchar2(30);
    recfile varchar2(30);
    rstts varchar2(30);
    rstfile varchar2(30);
    blksz number;
    rectab varchar2(30);
    rsttab varchar2(30);
    copyfile varchar2(30);
  begin
    datadir := 'FY_DATA_DIR';
    prepare_files(tgtowner, tgttable, datapath, datadir, rects, recfile, rstts, rstfile, blksz);
    rects := 'FY_REC_DATA';
    rstts := 'FY_RST_DATA';
    recfile := 'FY_REC_DATA.DAT';
    fill_blocks(tgtowner, tgttable, datadir, rects, recfile, rstts, 8, tgtowner, tgtowner, rectab, rsttab, copyfile);
    recover_table(tgtowner, tgttable, tgtowner, rectab, tgtowner, rsttab, datadir, datadir, recfile, datadir, copyfile, blksz, fbks, 'myself');
  end;
  procedure test_rec2( tow varchar2 default 'SYS',
                       ttb varchar2 default 'T_CHAIN',
                       fbks number default 1)
  as
    tgtowner varchar2(30):= upper(tow);
    tgttable varchar2(30):= upper(ttb);
    datadir varchar2(30);
    rects varchar2(30);
    recfile varchar2(30);
    rstts varchar2(30);
    blksz number;
    rectab varchar2(30);
    rsttab varchar2(30);
    copyfile varchar2(30);
  begin
    datadir := 'FY_DATA_DIR';
    rects := 'FY_REC_DATA';
    rstts := 'FY_RST_DATA';
    recfile := 'FY_REC_DATA.DAT';
    clean_up_ts(rects, rstts);
    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;
    fill_blocks(tgtowner, tgttable, datadir, rects, recfile, rstts, 8, tgtowner, tgtowner, rectab, rsttab, copyfile);
    recover_table(tgtowner, tgttable, tgtowner, rectab, tgtowner, rsttab, datadir, datadir, recfile, datadir, copyfile, blksz, fbks, 'myself');
  end;
begin
  null;
end pg_lan_recover_tab_data;
/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用ODU恢复TruncateODUmanual ODU3月 15th, 2009 意外Truncate的事情时有发生,ODU提供了方便的恢复Truncate的功能。被Truncate,只要原来的空间没有被重用(即数据被覆盖),则数据都是可以恢复的。 如果发现一个被意外地Truncate,而需要马上恢复。首先要做的就是关闭数据库,或者OFFLINE那个所在的空间,或者关闭所有应用。目的只有一个,确保空间不会被重用,数据不会被覆盖。 下面举例说明如何用ODU恢复Truncate掉的。 1. 建立测试的DB_JJ_INFO_TEMP。 SQL> connect pdata/test 已连接。 SQL> create table DB_JJ_INFO_TEMP as select * from dba_objects; SQL> truncate table DB_JJ_INFO_TEMP; 2. 我们OFFLINE掉DB_JJ_INFO_TEMP空间(实际上在实际的系统中,如果有比较多的活动,则空间不容易被OFFLINE下来)。然后做一个Checkpoint,让ODU能够读到最新的数据字典数据。 SQL> select tablespace_name from user_tables where table_name='DB_JJ_INFO_TEMP'; TABLESPACE_NAME ------------------------------ PDATA SQL> alter tablespace PDATA offline; 空间已更改。 SQL> alter system checkpoint; 系统已更改。 22=================================完善字典文件格式如下 control.txt文件中的数据格式为: 空间号 文件号 相对文件号 文件名 块大小 是否大文件空间 每列之间用空白分隔,可以只需要前四列,即块大小和是否大文件空间可省略,块大小省略时,数据文件的默认块大小为config.txt中block_size的大小。下面是一个示例的数据: #ts #fno #rfno filename block_size bigfile 0 1 1 D:\ORACLE\ORADATA\XJ\SYSTEM01.DBF 4096 1 2 2 D:\ORACLE\ORADATA\XJ\UNDOTBS01.DBF 4096 3 3 3 D:\ORACLE\ORADATA\XJ\DRSYS01.DBF 4096 4 4 4 D:\ORACLE\ORADATA\XJ\EXAMPLE01.DBF 4096 5 5 5 D:\ORACLE\ORADATA\XJ\INDX01.DBF 4096 6 6 6 D:\ORACLE\ORADATA\XJ\ODM01.DBF 4096 7 7 7 D:\ORACLE\ORADATA\XJ\TOOLS03.DBF 4096 7 8 8 D:\ORACLE\ORADATA\XJ\TOOLS02.DBF 4096 9 9 9 D:\ORACLE\ORADATA\XJ\XDB01.DBF 4096 11 10 10 D:\ORACLE\ORADATA\XJ\TEST01.DBF 2048 14 11 11 D:\ORACLE\ORADATA\XJ\K16.DBF 16384 如果数据文件头是完好的,则ODU会自动从文件头里面获取空间号,文件号,相对文件号,文件块大小等。空间号,文件号和相对文件号可以写为0。 注意:ODU将检查control.txt文件中的第一个数据文件是否为SYSTEM空间文件,所以要将SYSTEM空间的第1个文件放在control.txt文件中的第一行。否则将不能自动获得数据字典数据。 3. 运行ODU,并unload数据字典。 ODU> unload dict get_bootstrap_dba: compat header size:12 CLUSTER C_USER# file_no: 1 block_no: 177 TABLE OBJ$ file_no: 1 block_no: 241 CLUSTER C_OBJ# file_no: 1 block_no: 49 CLUSTER C_OBJ# file_no: 1 block_no: 49 found IND$’s obj# 19 found IND$’s dataobj#:2,ts#:0,file#:1,block#:49,tab#:3 found TABPART$’s obj# 230 found TABPART$’s dataobj#:230,ts#:0,file#:1,block#:3313,tab#:0 found INDPART$’s obj# 234 found INDPART$’s dataobj#:234,ts#:0,file#:1,block#:3377,tab#:0 found TABSUBPART$’s obj# 240 found TABSUBPART$’s dataobj#:240,ts#:0,file#:1,block#:3473,tab#:0 found INDSUBPART$’s obj# 245 found INDSUBPART$’s dataobj#:245,ts#:0,file#:1,block#:3553,tab#:0 found IND$’s obj# 19 found IND$’s dataobj#:2,ts#:0,file#:1,block#:49,tab#:3 found LOB$’s obj# 156 found LOB$’s dataobj#:2,ts#:0,file#:1,block#:49,tab#:6 found LOBFRAG$’s obj# 258 found LOBFRAG$’s dataobj#:258,ts#:0,file#:1,block#:3761,tab#:0 4. 获取PDATA用户下的DB_JJ_INFO_TEMP,也就是我们要恢复的信息: ODU> desc PDATA.DB_JJ_INFO_TEMP Object ID:33547 Storage(Obj#=33547 DataObj#=33549 TS#=11 File#=10 Block#=1400 Cluster=0) NO. SEG INT Column Name Null? Type --- --- --- ------------------------------ --------- ------------------------------ 1 1 1 OWNER VARCHAR2(30) 2 2 2 OBJECT_NAME VARCHAR2(128) 3 3 3 SUBOBJECT_NAME VARCHAR2(30) 4 4 4 OBJECT_ID NUMBER 5 5 5 DATA_OBJECT_ID NUMBER 6 6 6 OBJECT_TYPE VARCHAR2(18) 7 7 7 CREATED DATE 8 8 8 LAST_DDL_TIME DATE 9 9 9 TIMESTAMP VARCHAR2(19) 10 10 10 STATUS VARCHAR2(7) 11 11 11 TEMPORARY VARCHAR2(1) 12 12 12 GENERATED VARCHAR2(1) 13 13 13 SECONDARY VARCHAR2(1) 从上面的输出中,我们可以看到,PDATA.DB_JJ_INFO_TEMP所在的空间号为11,数据段头部为10号文件的1400号块。 5. 接下来用ODU扫描空间的extent: ODU> scan extent tablespace 11 scanning extent… scanning extent finished. 6. 我们使用ODU来确定DB_JJ_INFO_TEMP原来的data object id。一般来说,数据段的数据块,一般是在段头后面相邻的块中。但是我们可以从段头来确认: ODU> dump datafile 10 block 1400 Block Header: block type=0×23 (ASSM segment header block) block format=0×02 (oracle 8 or 9) block rdba=0×02800578 (file#=10, block#=1400) scn=0×0000.00286f2d, seq=4, tail=0×6f2d2304 block checksum value=0×0=0, flag=0 Data Segment Header: Extent Control Header ------------------------------------------------------------- Extent Header:: extents: 1 blocks: 5 last map: 0×00000000 #maps: 0 offset: 668 Highwater:: 0×02800579 (rfile#=10,block#=1401) ext#: 0 blk#: 3 ext size:5 #blocks in seg. hdr’s freelists: 0 #blocks below: 0 mapblk: 0×00000000 offset: 0 -------------------------------------------------------- Low HighWater Mark : Highwater:: 0×02800579 ext#: 0 blk#: 3 ext size: 5 #blocks in seg. hdr’s freelists: 0 #blocks below: 0 mapblk 0×00000000 offset: 0 Level 1 BMB for High HWM block: 0×02800576 Level 1 BMB for Low HWM block: 0×02800576 -------------------------------------------------------- Segment Type: 1 nl2: 1 blksz: 2048 fbsz: 0 L2 Array start offset: 0×00000434 First Level 3 BMB: 0×00000000 L2 Hint for inserts: 0×02800577 Last Level 1 BMB: 0×02800576 Last Level 1I BMB: 0×02800577 Last Level 1II BMB: 0×00000000 Map Header:: next 0×00000000 #extents: 1 obj#: 33549 flag: 0×220000000 Extent Map ------------------------------------------------------------- 0×02800576 length: 5 Auxillary Map ------------------------------------------------------------- Extent 0 : L1 dba: 0×02800576 Data dba: 0×02800579 ------------------------------------------------------------- Second Level Bitmap block DBAs ------------------------------------------------------------- DBA 1: 0×02800577 从上面的输出中的“Extent 0 : L1 dba: 0×02800576 Data dba: 0×02800579”可以看到,段的第1个数据块的RDBA为0×02800579,也就是10号文件的1401块。 我们dump第10号文件的1401块头,来得到DB_JJ_INFO_TEMP原来的data object id: ODU> dump datafile 10 block 1401 header Block Header: block type=0×06 (table/index/cluster segment data block) block format=0×02 (oracle 8 or 9) block rdba=0×02800579 (file#=10, block#=1401) scn=0×0000.00285f2b, seq=2, tail=0×5f2b0602 block checksum value=0×0=0, flag=0 Data Block Header Dump: Object id on Block? Y seg/obj: 0×830b=33547 csc: 0×00.285f21 itc: 3 flg: E typ: 1 (data) brn: 0 bdba: 0×2800576 ver: 0×01 Itl Xid Uba Flag Lck Scn/Fsc 0×01 0xffff.000.00000000 0×00000000.0000.00 C--- 0 scn 0×0000.00285f21 0×02 0×0000.000.00000000 0×00000000.0000.00 ---- 0 fsc 0×0000.00000000 0×03 0×0000.000.00000000 0×00000000.0000.00 ---- 0 fsc 0×0000.00000000 Data Block Dump: ================ flag=0×0 -------- ntab=1 nrow=16 frre=-1 fsbo=0×32 ffeo=0×145 avsp=0×113 tosp=0×113 可以看到,DB_JJ_INFO_TEMP原来的data object id就是33547。 7. 使用ODU来unload数据: ODU> unload table PDATA.DB_JJ_INFO_TEMP object 33547 Unloading table: DB_JJ_INFO_TEMP,object ID: 33547 Unloading segment,storage(Obj#=33547 DataObj#=33547 TS#=11 File#=10 Block#=1400 Cluster=0) 8. 使用sqlplus将PDATA空间ONLINE: SQL> alter tablespace test online; 空间已更改。 9. 使用sqlldr导入我们恢复的数据: E:\ODU\data>sqlldr test/test control=TEST_T1.ctl SQL*Loader: Release 9.2.0.8.0 - Production on 星期日 3月 15 15:13:56 2009 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. 达到提交点,逻辑记录计数6502 达到提交点,逻辑记录计数13004 达到提交点,逻辑记录计数19506 达到提交点,逻辑记录计数26008 达到提交点,逻辑记录计数30071 至此,恢复数据的步骤已经完成。我们来对比一下数据,看看数据是否和被Truncate前的数据完全一样: SQL> select * from t2 minus select * from DB_JJ_INFO_TEMP; 可以看到,数据已经完全恢复
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、付费专栏及课程。

余额充值