oracle数据库灾难挽救应急方案之DDL误操作恢复(drop)

1、核心业务表误drop恢复(no purge)

1.1模拟drop误操作

创建jason业务表,如下:

SQL> create table jason as select * from dba_objects;
SQL> create index idx_jason_oid on jason(object_id);

用户在删除数据的时候,不小心误删jason业务表,如下:

SQL> drop table jason;

1.2通过回收站恢复误删数据

首先,检查误删业务表当前所属用户的回收站,如下:

SQL> select object_name,original_name,operation,type,ts_name,droptime from user_recyclebin;

如果运气较好,被误删的业务表还保留在回收站中,那么可以通过回收站进行还原恢复,如下:

SQL> flashback table "BIN$ggkFrmhwKEngUwjSqMDkqA==$0" to before drop rename to JASON_NEW;
SQL> alter table JASON rename to JASON_OLD;
SQL> alter table JASON_NEW rename to JASON;

检查回收站功能如果开启,可选择关闭,如下:

SQL> ALTER SESSION SET recyclebin = OFF;
SQL> ALTER SYSTEM SET recyclebin = OFF SCOPE = SPFILE;

检查回收站功能如果关闭,可选择开启,如下:

SQL> ALTER SESSION SET recyclebin = ON;
SQL> ALTER SYSTEM SET recyclebin = ON SCOPE = SPFILE;

2、核心业务表误drop恢复(purge)

2.1模拟drop误操作

创建jason业务表,如下:

SQL> create tablespace tabtest datafile '/home/oracle/test/oradata/tabtest01.dbf' size 10m autoextend on;
SQL> create table tabtest tablespace tabtest as select * from dba_objects;

用户在删除数据的时候,不小心误删jason业务表,如下:

SQL> drop table tabtest purge;


2.2将误删表所在的表空间设置为只读

由于表已经误删除,因此需要和用户或业务开发等相关人员确认该业务表所在的表空间,并尽快将此业务表空间设置为只读模式,在设置之前需要创建与tabtest表拥有相同表结构的tabtest_old表,如下:

SQL> alter system set deferred_segment_creation=false;
SQL> create table tabtest_old tablespace tabtest as select * from dba_objects where 1=2;
SQL> alter tablespace tabtest read only;


2.3临时在SYSTEM表空间创建恢复表

由于tabtest表数据恢复,势必要涉及到IO读写操作,如果数据量不大,可临时设置恢复表的默认表空间为SYSTEM;如果数据量很大,建议为恢复表创建新的默认表空间,如下:

 SQL> create table tabtest_new tablespace system as select * from dba_objects where 1=2;


2.4通过日志挖掘获取误删表data_object_id

首先,手动切换归档日志,并记录切换后时间戳,如下:

SQL> alter system switch logfile;
SQL> alter system switch logfile;
SQL> select sysdate from dual;
SYSDATE
-------------------
2022-06-20 15:07:11

检查误操作发生至归档手动切换后系统生成的归档日志,如下:

col name for a60
set line 200
select unique NAME, THREAD#, SEQUENCE#, COMPLETION_TIME
  from gv$archived_log
 where thread# = 1
   and COMPLETION_TIME >=
       to_date('2022-05-09 13:15:03', 'yyyy-mm-dd hh24:mi:ss')
   and COMPLETION_TIME <=
       to_date('2022-05-09 13:24:47', 'yyyy-mm-dd hh24:mi:ss')
 order by SEQUENCE#;


添加第一个要加载的归档日志文件,如下:

SQL> execute dbms_logmnr.add_logfile(logfilename=>'/home/oracle/test/arch/1_84_1106134187.dbf',options=>dbms_logmnr.new);

多次添加要加载的归档日志文件,如下:

SQL> execute dbms_logmnr.add_logfile(logfilename=>'/home/oracle/test/arch/1_85_1106134187.dbf',options=>dbms_logmnr.addfile);

执行日志挖掘分析,如下:

SQL> execute dbms_logmnr.start_logmnr(options=>dbms_logmnr.dict_from_online_catalog);

查询v$logmnr_contents动态性能视图,获取归档日志解析信息,如下:

SQL> select count(*) from v$logmnr_contents;
  COUNT(*)
----------
    93190
SQL> create table sys.hzmc_logmnr_contents_fordrop as select * from v$logmnr_contents;

关闭日志挖掘分析,如下:

SQL> execute dbms_logmnr.end_logmnr;

获取drop purge动作执行的时间,如下:

SQL> select scn,timestamp,sql_redo from sys.hzmc_logmnr_contents_fordrop where operation='DDL' and lower(sql_redo) like '%tabtest%' order by timestamp desc;
       SCN TIMESTAMP           SQL_REDO
---------- ------------------- ---------------------------
 266498191 2022-06-20 14:55:43 drop table tabtest purge;

获取误删业务表data_object_id,如下:

SQL> select scn,timestamp,sql_redo from sys.hzmc_logmnr_contents_fordrop where timestamp like '2022-06-20 14:55%' and sql_redo like 'delete%';

2.5通过ROWID尝试找回误删表数据

DBMS_ROWID.ROWID_CREATE包的语法,如下:

DBMS_ROWID.ROWID_CREATE (rowid_type IN NUMBER,object_number IN NUMBER,relative_fno IN NUMBER,block_number IN NUMBER,row_number IN NUMBER) RETURN ROWID;

DBMS_ROWID.ROWID_CREATE传参解析,如下:

rowid_type Type (restricted or extended). Set the rowid_type parameter to 0 for a restricted ROWID. Set it to 1 to create an extended ROWID. If you specify rowid_type as 0, then the required object_number parameter is ignored, and ROWID_CREATE returns a restricted ROWID.

object_number Data object number (rowid_object_undefined for restricted).

relative_fno Relative file number.

block_number Block number in this file.

row_number Returns row number in this block.


将tabtest_old表的dataobj#修改为2.2.4节获取到的dataobj#,如下:

SQL> select obj#,dataobj# from obj$ where name='TABTEST_OLD';
SQL> update obj$ set DATAOBJ#=89741 where obj#=89744;
SQL> commit;

获取tabtest_old表所在的数据文件号和段头块号,如下:

set linesize 200 pagesize 200
col owner for a10
col segment_name for a15
select owner, segment_name, header_file, header_block, segment_type
  from dba_segments
 where segment_name = 'TABTEST_OLD';
OWNER      SEGMENT_NAME    HEADER_FILE HEADER_BLOCK SEGMENT_TYPE
---------- --------------- ----------- ------------ ------------------
SYS        TABTEST_OLD               8          130 TABLE

先通过dbms_rowid.ROWID_CREATE包获取第8号数据文件的第131数据块的第1行ROWID,再根据ROWID返回TABTEST表的object_id字段值,通过与dba_objects查询对比,发现一致,说明通过该方式可以尝试找回误删表的数据,如下:

alter system flush shared_pool;
alter system flush shared_pool;
alter system flush buffer_cache;
alter system flush buffer_cache;
SQL> select object_id from tabtest_old where rowid=dbms_rowid.ROWID_CREATE(1,89741,8,131,0);
 OBJECT_ID
----------
        20
SQL> select object_id from dba_objects where rownum<=1;
 OBJECT_ID
----------
        20


2.6通过PL/SQL尝试找回所有误删表数据

SET SERVEROUTPUT ON
DECLARE
   nrows number;
   rid rowid;
   objd number;
   ROWSPERBLOCK number;
  BEGIN
   ROWSPERBLOCK:=1000;
   nrows:=0;
   for i in (
select file_id,block_id,blocks from dba_free_space where tablespace_name='TABTEST'
 union all                                                                    
select file_id,block_id,8 blocks from dba_extents where segment_name='TABTEST_OLD'
            )
   loop
     for fblkno in i.block_id..i.block_id+i.blocks-1 loop
      for fblkrow in 1..ROWSPERBLOCK loop
      begin
        rid := dbms_rowid.ROWID_CREATE(1,89741,i.file_id, fblkno,fblkrow-1);
        insert into sys.tabtest_new
        select *
        from sys.tabtest_old a
        where rowid = rid;
        if sql%rowcount = 1 then nrows:=nrows+1; end if;
        if (mod(nrows,1000)=0) then commit; end if;
      exception when others then null;
      end;
      end loop;
    end loop;
   end loop;
   COMMIT;
   dbms_output.put_line('Total rows: '||to_char(nrows));
  END;
  /
Total rows: 86726
PL/SQL procedure successfully completed.


待数据找回后,再将tabtest_new表重命名为tabtest表,在业务低峰期再对tabtest表做表空间迁移,如下;

SQL> alter table tabtest_new rename to tabtest;
SQL> alter tablespace tabtest read write;
SQL> alter table tabtest move tablespace tabtes


请使用浏览器的分享功能分享到微信等