是不是我们的数据库,加上一套成熟可靠的备份软件(比如NBU、DP、TSM等),以及购置了可靠的大容量的带库就足够了?或者下面一个案例能够给我们一些启示。

案例来自于一个老客户,一套重要系统的Oracle RAC数据库,由于硬件问题,一个包含关键业务数据的文件被离线(在归档模式下,写文件出错会导致文件被置为离线状态,而不是库宕掉)。在尝试recover datafile的时候,提示缺少一个归档日志。归档日志已经被备到带库上,本地磁盘上已经没有了这个归档日志文件。

这套库是用TSM备份的,使用rman还原归档日志,称找不到这个归档日志。看起来出问题了,在rman中用下面的命令:

list backup of archivelog sequence 18884 thread 2;

返回的结果说没有找到这个归档日志的备份。甚至于用命令:

list backup of archivelog all;

发现好些归档日志没有了备份。但是这些文件又不在本地磁盘上。那么,这里有几种可能:

  1. 归档日志被人为地删除,根本没有备份
  2. 归档日志的备份已经被删除,通过delete backup命令

第1种情况,可以从v$archived_log视图判断归档日志到底有没有备份(通过BACKUP_COUNT列)。我们可以从备份保留的日志中判断第2种情况是否存在。

检查备份操作的日志,发现恢复所需要的归档日志文件是成功备份了的。那备份怎么消失了?在备份操作的日志目录中,还发现一个日志文件有crosscheck backup然后delete expired backup的记录,而被删除的备份正好有恢复所需要的归档日志所在的备份。所以,这里可以知道,出现了上述说的第2种情况,备份被删除了。

为什么会出现备份在crosscheck backup之后成为expired状态,这个结果就来源于在rman中进行crosscheck backup时,备份服务器返回的结果表明这个备份不可访问了,或许是权限问题,或者是配置不当,或者是备份文件真的不能访问了。从目前的情况来看,备份都是成功的,看上去带库、备份服务器都是好的。不过这里值得注意的是,这是一套RAC数据库,归档日志是在节点1上完成的,在节点1上也进行了crosscheck backup,并且是先进行crosscheck,而其结果表明备份是available状态的。但是随后节点2的crosscheck的结果是expired,那只能说明由于权限或配置问题,导致节点2不能访问到节点1所做的备份(当然不排除在这个时间窗口内备份在带库上或备份服务端删除的可能,但是可能性较小,所以分析问题得先从可能性更大的入手)。

是不是没救了?答案在于,备份到底还在不在带库上?

节点1先crosscheck正常,随后节点2 crosscheck称备份文件没有或不可访问,然后节点2删除了备份。只不过这里要注意的是:既然crosscheck不能访问不到备份,那么delete操作也应当不会真正删除备份(备份都访问不到怎么能物理删除呢?),只是把备份信息从catalog里面删除掉而已。所以这里的结论是真正的备份还在带库上。可以找备份管理员或通过TSM命令来检查,不过客户说,搞TSM的人找不到了。

接下来,尝试找找看,有没有在备份归档日志之后,但在删除备份之前的备份控制文件存在。可惜没有,如果有,可以用这个控制文件来还原归档日志。
或许可以通过手工在catalog库里面添加记录,然后同步到控制文件来进行恢复。
不过我们还有另一个方法,就是直接使用dbms_backup_restore包:

DECLARE 
v_dev varchar2(50);
v_done boolean:=false;
BEGIN 
v_dev:=sys.dbms_backup_restore.deviceAllocate(type=>'sbt_tape',ident=>'t1',params=>'ENV=(TDPO_OPTFILE=/usr/tivoli/tsm/client/oracle/bin64/tdpo.opt)'); 
dbms_backup_restore.RestoreSetArchivedLog(destination=>'/arch');
dbms_backup_restore.RestoreArchivedLog(thread=>2,sequence=>18884);
dbms_backup_restore.RestoreBackupPiece(done => v_done,handle => 'xxx_archlog_backup<xxxx1_7941 :796937510>.dbf', params => null);
sys.dbms_backup_restore.deviceDeallocate; 
END; 

幸运的是,归档日志成功还原,然后数据文件成功recover。

从这个案例中,我们获得的是:

  • 并不是说,备份没有报错,备份正常运行就足够了。在备份的时候,为了避免备份出错而失败,在备份之前进行crosscheck archivelog,把人为删除掉的归档从catalog中去掉从而不备份,也就在备份时不报错;或者是备份时skip inaccessible;实际上这有点类似于掩耳盗铃,备份可能是残缺的,根本不可用。
  • rman中的crosscheck backup,使得backup成为expired状态,这本身说明可能存在问题,而不仅仅是从catalog中删除备份了事。针对这个案例来说,backup成为expired,本身就是一种异常,就应该要去检查备份服务器的配置等。
  • 所有涉及备份相关的操作,包括备份,删除备份,crosscheck备份,保留详细的rman日志是非常有用的。
  • 应该在每次备份后,对控制文件进行一次备份;打开控制文件的AUTO BACKUP也是有必要的。

--The End.

此时此刻,我迎来了2010年的第一个加班。在这么一个杯具性时刻,还是要祝所有的朋友们,新年快乐!

回顾2009年,用一句简单的话来说就是,“这一年没有白过”。

看看这一年,有些什么样的收获:

自己开发的ODU软件,走向成熟,得到了许多朋友的认可,也帮助很多朋友解决了燃眉之急。从年初发布v2.0.1版,到支持压缩表,中间相隔了3个月的时间。从那之后,就是一些功能的完善和BUG的修复,到如今已经相对比较稳定。通过开发这个软件,我自己对ORACLE的块格式有了相当清楚地了解,同时这个软件也发挥了他独特的作用,帮助一些朋友恢复了数据。

与盖国强、杨廷锟、段林仲、邹德平合作的《Oracle DBA手记》,即将面世。这完全是一个意外。通过这样的写作,收获是巨大的。我也希望除BLOG之外方式的知识分享,能够让更多的从事Oracle数据库相关工作的朋友受益。

技术方面,理论知识方面进展不大,但是实战经验值大幅提升。学习,从来都是实践与理论相结合。新的一年,争取有更进一步的突破。

作了房奴,收了房,也有了代步的工具。压力是越来越大了。争取明年七八月时能住进新房。

这一年,也是辛苦的一年。特别是最近2个月,加班与通宵已经成为十分常见的活动,无奈的是,绝大多数时候没办法控制这样的事情的发生。

2010年,我希望也有更多的收获:

  • 争取把OCM给过了。谁叫这玩意儿商业需求是如此的旺盛。
  • 多写一些有价值的技术文章。
  • 接触接触Oracle之外的东西。

本文简单记录一下最近一次数据恢复的过程。

事情的起因是,一个应用升级后,某一个操作导致一个表的几个列全部被更新为同一值(忍不住又要唠叨测试的重要性)。这样的错误居然出现在应用代码中,显然是重大的BUG。那个是罪魁祸首的SQL,UPDATE语句,其WHERE条件仅仅只有一个where 1=1。

系统的维护人员称是星期五出的错,发现出错是在星期天,也就是我恢复数据的日期,与声称的出错时间已经隔了将近2天。开始尝试用flashback query恢复数据,报ORA-01555错误,此路不通。维护人员说,星期五之前的RMAN备份已经被删除了(又是一个备份恢复策略不当地例子),使用基于时间点的恢复也不可能了。剩下的一条路,只有使用log miner。还好归档文件还在数据库服务器上。

这套库是一套RAC数据库,由于没有人能确认操作发生在哪个节点,因此需要将一个节点下所有的归档复制到另一个节点上(如果没有足够的空间,可以使用NFS)。然后需要找到我们用于数据恢复的归档日志:

set linesize 170 pagesize 10000
alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';

col name for a30
col first_change for a10
col next_change for a10

select max(first_time) from v$archived_log 
where first_time < to_date('200909251900','yyyymmddhh24mi'); --这里的时间为错误发生时估计的最早时间。

select sequence#,first_time,name,to_char(first_change#,'xxxxxxxx') first_change,
 to_char(next_change#,'xxxxxxxx') next_change
 from v$archived_log 
where  first_time >=to_date('200909251707','yyyymmddhh24mi')  
order by 2;--这里的时间为前一SQL的max(first_time)结果

 SEQUENCE# FIRST_TIME          NAME                           FIRST_CHAN NEXT_CHANG
---------- ------------------- ------------------------------ ---------- ----------
      4039 2009-09-25 17:07:10 /arch/db1_1_4039.arc          88ce7eff   88d1457c
      4040 2009-09-26 12:24:52 /arch/db1_1_4040.arc          88d1457c   88d1459f
      4041 2009-09-26 12:25:22 /arch/db1_1_4041.arc          88d1459f   88d156a4
      4688 2009-09-26 12:37:59 /arch/db1_2_4688.arc          88d1457f   88d1464a
      4689 2009-09-26 12:38:27 /arch/db1_2_4689.arc          88d1464a   88d1569c
      4042 2009-09-26 12:54:44 /arch/db1_1_4042.arc          88d156a4   88d157e7
      4043 2009-09-26 12:54:56 /arch/db1_1_4043.arc          88d157e7   88d1ab06
      4690 2009-09-26 13:07:47 /arch/db1_2_4690.arc          88d1569c   88d1570b
      4691 2009-09-26 13:08:00 /arch/db1_2_4691.arc          88d1570b   88d1ab09
      4044 2009-09-26 15:27:32 /arch/db1_1_4044.arc          88d1ab06   88d1ab0d
      4045 2009-09-26 15:27:35 /arch/db1_1_4045.arc          88d1ab0d   88d25091
      4692 2009-09-26 15:40:36 /arch/db1_2_4692.arc          88d1ab09   88d1ab77
      4693 2009-09-26 15:40:39 /arch/db1_2_4693.arc          88d1ab77   88d25094
      4046 2009-09-26 22:24:07 /arch/db1_1_4046.arc          88d25091   88d250db
      4047 2009-09-26 22:24:19 /arch/db1_1_4047.arc          88d250db   88d2515e
      4048 2009-09-26 22:24:29 /arch/db1_1_4048.arc          88d2515e   88d25167
      4049 2009-09-26 22:24:41 /arch/db1_1_4049.arc          88d25167   88d25cac
      4694 2009-09-26 22:37:13 /arch/db1_2_4694.arc          88d25094   88d25147
      4695 2009-09-26 22:37:25 /arch/db1_2_4695.arc          88d25147   88d2515b
      4696 2009-09-26 22:37:33 /arch/db1_2_4696.arc          88d2515b   88d2516a
      4697 2009-09-26 22:37:47 /arch/db1_2_4697.arc          88d2516a   88d25ca9
      4050 2009-09-26 22:41:57 /arch/db1_1_4050.arc          88d25cac   88d25cde
      4698 2009-09-26 22:55:01 /arch/db1_2_4698.arc          88d25ca9   88d25dcf
      4699 2009-09-26 22:55:19 /arch/db1_2_4699.arc          88d25dcf   88dbd27e

尝试找到数据被错误更新的时间点:

exec sys.dbms_logmnr.add_logfile(logfilename=>'/arch/db1_1_4038.arc');
exec sys.dbms_logmnr.add_logfile(logfilename=>'/arch/db1_1_4039.arc');

exec sys.dbms_logmnr.start_logmnr(options=>sys.dbms_logmnr.dict_from_online_catalog);

col sql_redo for a50

select scn,timestamp,username,sql_redo from v$logmnr_contents 
where operation='UPDATE' and upper(sql_redo) like '%TBL_FORM_FORM%' 
and sql_redo like '%SGS0900021BNc10%'  --这个值是UPDATE时某一列被更新后的值,用在这里便于查找。
order by scn,timestamp;
exec sys.dbms_logmnr.end_logmnr;

很不幸的是,没有找着需要的数据。再往后找了几个日志,也没找着。
如果一直找下去,显然会消耗比较长的时间,业务也已经停止了。不过可以用一种简单的方法来查找数据被错误更新发生的时间:一个比较大的表,通常段头后面的那个块,也就是存储那个表的数据的第1个块,通常是很少更新的,至少当时恢复的那个表是这样一种情况。我们可以通过数据块中ITL上的事务SCN来满足我们的要求。

SQL> select tablespace_name,extent_id,file_id,block_id,blocks
     from dba_extents where owner='XXX'
     and segment_name='TBL_FORM_FORM'
     order by extent_id;

TABLESPACE_NAME   EXTENT_ID    FILE_ID   BLOCK_ID  BLOCKS
---------------- ---------- ---------- ---------- -------
XXXX                      0         16      25481     128
XXXX                      1         17      23433     128
XXXX                      2         18      21385     128
XXXX                      3         19      19977     128
XXXX                      4         16      23945     128
XXXX                      5         17       8585     128
XXXX                      6         18      14217     128
XXXX                      7         19      18825     128

SQL> alter system dump datafile 16 block 25482;

System altered.

Start dump data blocks tsn: 4 file#: 16 minblk 25482 maxblk 25482
buffer tsn: 4 rdba: 0x0400638a (16/25482)
scn: 0x0000.88e21027 seq: 0x02 flg: 0x00 tail: 0x10270602
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump:  0x0400638a
 Object id on Block? Y
 seg/obj: 0x40d8  csc: 0x00.88e20c40  itc: 2  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0010.011.0006ed74  0x03c002a0.2f48.07  C---    0  scn 0x0000.88d7af30
0x02   0x0012.019.000027e0  0x03c00ede.05de.42  C---    0  scn 0x0000.44e2ee39

从上面的结果可以看到,数据块的ITL中,最新的事务其SCN为88d7af30,正处于最后一个归档日志的first_change#和last_change#之间,即88d25dcf和88dbd27e之间,难不成这个错误是今天早上才发生的?于是我挖掘最后1个归档日志,结果发生错误的确是发生在早上,也就是我开始进行恢复操作之前半个小时。

既然错误并没有发生太久,同时这个系统也允许一定的数据丢失,那就使用flashback query,得到UPDATE操作之前的数据即可。

create table tbl_form_form_new 
as select * from tbl_form_form
as of timestamp to_date('2009-09-27 09:08:00','yyyy-mm-dd hh24:mi:ss');
--当然这里也可以按SCN进行闪回。

幸运的是,这次闪回查询成功了。看起来足够大的UNDO表空间还是有好处,至少我已经有数次用闪回查询来恢复数据。

,

这篇文章看起来有点标题党的感觉。

昨天一位网友管理的数据库(版本9iR2,平台Windows),由于存储阵列问题,挂了,再也起不来了。

数据库本来有RMAN做的备份,但不幸的是,备份数据与数据库放在同一台服务器,同一个硬盘上,备份文件不幸地变成了0字节。

数据库在打开时,报ORA-1578错误,错误块为系统表空间文件号1的第417块。这可是系统在自举(bootstrap)时非常重要的一个数据块,具体可以参见eygle的文章《Oracle中独一无二的Cache对象》

使用dbv检查数据文件,发现很多很多的坏块。

我让网友对现有的数据库中的所有文件(数据文件,在线日志文件等)做一个冷备份。然后使用我开发的ODU,dump 文件1的第417块,”神奇的“事情出现了,dump出来的结果显示,这个块头显示这个块的地址居然是文件号1,块号为425,相差了8个块。继续检查发现,这个块附近的连续8个块,都偏移了8个块(均是向后偏移了),而这8个块之后的8个块,又向前偏移了8个块的位置。说的更清楚的就是,这连续的16个块,前8个块和后8个块,他们在磁盘上交换了位置

My God,这个系统疯了,是存储阵列的问题?还是操作系统的。看起来阵列的问题其可能性更大。

我花了大约20几分钟的时间,修改了一下ODU程序,对copy datafile命令加上了修正数据块交错的功能。网友用copy datafile将SYSTEM表空间的数据文件复制成一个新的文件后,用dbv检查,仍然还是有很多的坏块,不过已经比之前的少很多了。不过从dbv检查的结果来看,有很多的坏块,显示的是全0字节,也就是说,这个块中的所有数据全为0。这样的块,彻底地坏了。

不过用ODU修正块的交错之后,至少能导出一部分数据字典了,有了一线希望。

网友仍然在尝试恢复中。

这是一个惨痛的教训,系统一定要做备份,备份的数据一定不要放在与系统同一台服务器上,也一定不要放在同一个硬盘或阵列上,阵列也是不可靠的。

,

再过1小时,2008年就要过去了。
别了,2008
这一年里,发生了太多的事。
咱平民小百姓,不谈国家大事。谈谈自己的就好了。

2008年,在成都安定了下来。上班搞自己喜欢搞的ORACLE,下班回家陪陪父母、老婆和孩子。这日子,挺不错。
2009年,我会干什么呢?大家都会说计划没有变化快。看起来,我不用计划那么多事情了。唯一计划的是,为以后的发展储备更多的能量吧。

别了,2008
欢迎你,2009

一个客户的重要生产系统,一个USER被删除了,USER下所有的对象都被删除了。还好是下班时间,也有有效的备份,数据得以完全恢复,也没有对生产造成重大影响。

引起这个事故的,除了人为因素之外,ORACLE的BUG要负一半的责任。通过操作录像(客户对系统内做系统管理的所有机器的操作都作了录像,这点非常好),我们搞清楚了数据被删除的经过。

一个开发人员,通过OEM(Oracle Enterprise Manager Console)连接到数据库上,经过他确认OEM上的那个连接字符串是正确的,然后对USER做了删除操作,但很快发现,生产库的数据被删除了。操作录像也证明那个连接字符串是正确的,那么问题出在哪里呢?

操作的那台机器(Windows系统),在系统环境变量(我的电脑=>属性=>高级=>环境变量=>系统变量)中设置了TNS_ADMIN,指向了另外的目录。现在,TNS_ADMIN指向的目录(下面简称TNS_ADMIN目录)和%ORACLE_HOME%\NETWORK\ADMIN(下面简称ORACLE目录)下都有TNSNAMES.ORA这个文件。在TNS_ADMIN中,TNSNAMES.ORA有一TNSNAME,指向生产库。在ORACLE目录中,TNSNAMES.ORA中有一同样的名称的TNSNAME指向开发库。

OEM在处理TNS_ADMIN上是有问题的。OEM在启动后,在左边的数据库目录树,是从ORACLE目录的TNSNAMES.ORA中解析出来的,完全忽略了TNS_ADMIN环境变量,就算是执行”将数据库添加到树“操作,也是完全忽略了TNS_ADMIN变量,操作的是ORACLE目录中的TNSNAMES.ORA文件,显示的连接字符串信息也是从那个文件中得到的。下面是显示信息的截图:

然而,在用这个TNSNAME进行连接数据库时,却是按照TNS_ADMIN目录中的TNSNAMES.ORA文件的配置进行连接的,如果这两个TNSNAMES.ORA都有这个TNSNAME,那么不幸就发生了,本来我们期望是连接到OEM中显示的那个数据库上,结果却连接到了另一个库上。这可以是说OEM的重大BUG。

这里谈到的OEM是9i的版本,NetCA也有这个问题,但Net Manager没有这个问题。

事情虽然过去了,但是以下几件事情我们仍然值得我们牢记:

  • 有效的备份,特别是归档模式下的有效物理备份,是保证数据不会被丢失的前提。
  • 数据库用户权限的管理,需要遵循”最少权限“的原则,不可忽视。很多数据库管理人员为了方便,给Oracle用户太大的权限,甚至是DBA角色权限。这是非常危险的。
  • 有了备份仍然不够,需要做恢复测试,避免出现问题发现备份不能恢复,否则悔之晚矣。这次事故中,由于第三方的备份软件问题,导致数据恢复至少多花了三个小时的时间。要是之前有做过完整的测试,则会发现备份软件的问题。
  • 一些危险的操作,如删除用户,删除表等操作,一定要有规范的流程,确认无误后再执行。

还有以下我的一些个人观点:

  • 数据库管理时,尽量少用图形化的软件,一次DEL按键就能葬送整个系统。
  • 尽量将生产系统、测试系统与开发库隔离,比如禁止在开发机器上直接连接生产库,开发完成后,需要部署到生产库时,遵循专门的流程进行。也就是要规范开发流程。