客户的一次疏忽,DBA的一次噩梦

运维 数据库运维 数据库
你是否如同作者一样,有过如下噩梦一般的经历呢?是否半夜被老板挖起来,只为了客户的一个小小的误操作?无论是否在你身上印证过,都可以来看看,作者写这篇文章之时已经熬了一夜,还能思路如此清晰,值得学习,佩服之~~~

今晚接到老大的电话,泰国的客户不小心删除了一些表的数据,现在非常着急,需要恢复数据。其实DBA做的数据库备份,很大程度是用于数据库crash掉的时候,恢复数据,而不是三天两头的因为客户误删了数据,而去做恢复。

看了客户的邮件,是有2个表的数据被误删除或者误插入或者误更新了。总之,操作过一大通,希望恢复到当天下午15:30的数据。上数据库去查了一下,用备份来恢复,似乎时间不够,尝试用户flashback query,发现已经回不去了:

SQL> SQL> SQL> SELECT count(*) from hr_ttm.TA_ABSDOCS  
  2  AS OF TIMESTAMP TO_TIMESTAMP('2011-06-09 15:29:00','YYYY-MM-DD HH24:MI:SS');  
SELECT count(*) from hr_ttm.TA_ABSDOCS  
                            *  
ERROR at line 1:  
ORA-01555: snapshot too old: rollback segment number 1 with name "_SYSSMU1$" 
too small 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

其他也没有更快的方法了,于是当下决定用logmnr挖数据,

由于数据库原来就没有配置utl_file_dir,因此还需要重启数据库使得该参数生效。一路做下来,大致算顺利,不过也遇到了不少小插曲。下面就是恢复的步骤:

一、备份原表

create table hr_ttm.TA_ABSDOCS_20110610_0010 as 
SELECT * from hr_ttm.TA_ABSDOCS;  
   
create table hr_ttm.TA_ABSDOC_20110610_0010 as 
SELECT * from hr_ttm.TA_ABSDOC; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

二,根据客户要求,建立新用户,将恢复的数据导入到这2个表中:

create user hr_ttm2 identified by hr_ttm2 default tablespace MSG_DATA;  
grant connect,resource,dba to hr_ttm2;  
  • 1.
  • 2.

三、把原表数据备份到新用户下,用于做回滚

create table hr_ttm2.TA_ABSDOCS as 
SELECT * from hr_ttm.TA_ABSDOCS  
   
create table hr_ttm2.TA_ABSDOC as 
SELECT * from hr_ttm.TA_ABSDOC 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

四、修改参数,用于挖日志,重启数据库

alter system set utl_file_dir='/prodlog/logmnr' scope=spfile;  
  • 1.

五、生成数据字典

exec dbms_logmnr_d.build('dictionary.ora','/prodlog/logmnr');  
  • 1.

做这一步之前注意需要修改LD_LIBRARY_PATH和LIBPATH,使得lib的变量在lib32前面。不然会有报错ORA-00600: internal error code, arguments: [unable to load XDB library], [], [], [], [], [], [], []

#p#

六、检查需要回滚的日志,客户要求回滚到6月9日15:30之前:

-rw-r-----    1 oracle   oinstall   48868352 Jun 09 14:53 ARC0000025854_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 15:08 ARC0000025855_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 15:17 ARC0000025856_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48910848 Jun 09 15:42 ARC0000025857_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 16:04 ARC0000025858_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 17:22 ARC0000025859_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 17:27 ARC0000025860_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 17:47 ARC0000025861_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 20:52 ARC0000025862_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 21:17 ARC0000025863_0666465023.0001  
drwxr-xr-x    2 oracle   oinstall        256 Jun 09 23:02 logmnr  
-rw-r-----    1 oracle   oinstall   48863744 Jun 09 23:07 ARC0000025864_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:28 ARC0000025865_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:29 ARC0000025866_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:29 ARC0000025867_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:29 ARC0000025868_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:29 ARC0000025869_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:30 ARC0000025870_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:30 ARC0000025871_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:30 ARC0000025872_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:31 ARC0000025873_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:31 ARC0000025874_0666465023.0001  
-rw-r-----    1 oracle   oinstall   48868864 Jun 09 23:31 ARC0000025875_0666465023.0001 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

 七、添加归档日志

exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025854_0666465023.0001',Options=>dbms_logmnr.new);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025855_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025856_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025857_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025858_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025859_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025860_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025861_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025862_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025863_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025864_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025865_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025866_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025867_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025868_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025869_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025870_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025871_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025872_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025873_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025874_0666465023.0001',Options=>dbms_logmnr.addfile);  
exec dbms_logmnr.add_logfile(LogFileName=>'/prodlog/logmnr/ARC0000025875_0666465023.0001',Options=>dbms_logmnr.addfile); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

八、开始挖日志

exec dbms_logmnr.start_logmnr(dictfilename=>'/prodlog/logmnr/dictionary.ora');  
  • 1.

九、将logmnr的数据暂时保存在一个表里面,免得再次查询时候不用再次添加归档日志

create table hjm_logmnr nologging as select * from v$logmnr_contents where 1=2;    
insert /*+ append */ into hjm_logmnr select * from v$logmnr_contents;    
/*------做这一步之前注意将nls_date_format改成'yyyy-mm-dd hh24:mi:ss',不然泰文乱码,时间会变成问号。------*/  
  • 1.
  • 2.
  • 3.

十、导出脚本,用脚本做回滚,注意SQL_UNDO中的delete语句末尾有rowid,不能直接用,需要用正则表达式替换掉。

spool TA_ABSDOCS_undosql.txt  
select regexp_replace(replace(SQL_UNDO,'"HR_TTM"','"HR_TTM2"'),'and ROWID.+;',';')  
from hjm_logmnr  
WHERE 
SEG_NAME = 'TA_ABSDOCS' AND 
SEG_OWNER = 'HR_TTM'   
order by to_char(TIMESTAMP,'yyyy-mm-dd hh24:mi:ss'desc;  
spool off 
   
   
spool TA_ABSDOC_undosql.txt  
select regexp_replace(replace(SQL_UNDO,'"HR_TTM"','"HR_TTM2"'),'and ROWID.+;',';')  
from hjm_logmnr  
WHERE 
SEG_NAME = 'TA_ABSDOC' AND 
SEG_OWNER = 'HR_TTM'   
order by to_char(TIMESTAMP,'yyyy-mm-dd hh24:mi:ss'desc;  
spool off 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

去掉脚本的头部的语句和末尾返回多少多少行的文字,在hr_ttm2下执行这2个脚本,实现数据回滚。

另外,我们来看一下:

SQL> select to_char(max(TIMESTAMP),'yyyy-mm-dd hh24:mi:ss'),to_char(min(TIMESTAMP),'yyyy-mm-dd hh24:mi:ss')  
from hjm_logmnr  
WHERE 
SEG_NAME = 'TA_ABSDOC' AND 
SEG_OWNER = 'HR_TTM'   2    3    4    5    
  6  /  
   
TO_CHAR(MAX(TIMESTA TO_CHAR(MIN(TIMESTA  
------------------- -------------------  
2011-06-09 21:20:26 2011-06-09 15:31:54  
   
SQL>   
SQL>   
SQL>   
SQL>   
SQL> l  
  1  select to_char(max(TIMESTAMP),'yyyy-mm-dd hh24:mi:ss'),to_char(min(TIMESTAMP),'yyyy-mm-dd hh24:mi:ss')  
  2  from hjm_logmnr  
  3  WHERE 
  4  SEG_NAME = 'TA_ABSDOC' AND 
  5* SEG_OWNER = 'HR_TTM' 
SQL> l4  
  4* SEG_NAME = 'TA_ABSDOC' AND 
SQL> c/TA_ABSDOC/TA_ABSDOCS  
  4* SEG_NAME = 'TA_ABSDOCS' AND 
SQL>   
   
TO_CHAR(MAX(TIMESTA TO_CHAR(MIN(TIMESTA  
------------------- -------------------  
2011-06-09 21:20:26 2011-06-09 15:37:39 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

归档日志是从14:53开始,而这2个表的变动,是在15:30之后才有的(min(TIMESTAMP是在15:30之后)。
也就是说从14:53分到15:30之前,这2个表一直是没动过的。因此,可以跟客户说,2个表的数据恢复到15:00的。

抬头一看,东方既白。唉,又熬了个通宵。

【小边碎语】以上是作者在月初的一次噩梦一般的经历,无论是否在你身上印证过,都可以来看看,作者真不愧是专业级人物,思路超清晰,值得学习和借鉴。

 

【编辑推荐】

  1. Oracle SQL:经典查询练手第一篇
  2. Oracle SQL:经典查询练手第二篇
  3. Oracle SQL:经典查询练手第三篇
  4. Oracle SQL:经典查询练手第四篇
  5. Oracle SQL:经典查询练手第五篇

 

 

 

责任编辑:艾婧 来源: OracleBlog
相关推荐

2021-12-27 10:08:16

Python编程语言

2020-10-24 13:50:59

Python编程语言

2012-08-28 09:21:59

Ajax查错经历Web

2021-11-01 17:29:02

Windows系统Fork

2011-04-07 11:20:21

SQLServer

2017-02-28 11:13:36

华为

2020-10-18 12:53:29

黑科技网站软件

2021-12-06 19:29:17

LRU内存算法

2009-02-06 09:50:00

DHCP地址分配

2017-08-24 17:37:18

DNS缓存分析

2020-03-18 13:07:16

华为

2017-01-23 12:40:45

设计演讲报表数据

2021-04-02 06:18:27

Docker镜像

2023-03-29 09:36:32

2015-07-17 10:04:33

MKMapView优化

2022-03-23 15:43:26

Android客户端架构

2025-03-17 10:01:07

2018-01-15 14:50:49

APP转让App账号

2020-03-10 07:51:35

面试讽刺标准

2013-01-17 10:31:13

JavaScriptWeb开发firebug
点赞
收藏

51CTO技术栈公众号