Oracle数据文件的坏块,可分为物理坏块和逻辑坏块。物理坏块(也可以称为介质坏块)指的是块格式本身是坏的,块内的数据没有任何意义。而逻辑坏块,指的是块内的数据在逻辑是存在问题。比如说索引块的索引值没有按从小到大排列。物理坏块一般是由于内存问题、OS问题、IO子系统问题和硬件引起,逻辑坏块一般是是由于Oracle Bug等原因引起。
Oracle数据文件的每个块,其块头为20字节。其定义如下:(来自于DSI401)
structkcbh
{
ub1type_kcbh;/*blocktype*/
ub2frmt_kcbh;
ub1spare1_kcbh;
ub1spare2_kcbh;
krdbardba_kcbh;/*relativeDBA*/
ub4bas_kcbh;/*baseofSCN*/
ub2wrp_kcbh;/*wrapofSCN*/
ub1seq_kcbh;/*sequence#ofchangesatthesamescn*/
ub1flg_kcbh;
ub2chkval_kcbh;
};
在块头中,seq_kcbh(占用1字节,块头偏移14)有着特殊的含义,如果该值为0xff,则表示该块被标记为corruption。
下面我们做一个测试:
SQL> create table test.t1 as select * from dba_objects;
表已创建。
SQL> select header_file,header_block from dba_segments where segment_name=’T1′ and owner=’TEST’;
HEADER_FILEHEADER_BLOCK
-----------------------
101445
修改db_block_checksum参数值为TRUE,关闭数据库,我们用ultraedit修改10号文件的1447块的check sum(一个随便>0的数)及flag=0×04。然后再打开数据库。再执行下面的查询:
SQL> select count(*) from test.t1;
select count(*) from test.t1
*
ERROR 位于第 1 行:
ORA-01578: ORACLE 数据块损坏(文件号10,块号1447)
ORA-01110: 数据文件 10: ‘D:\ORACLE\ORADATA\XJ\TEST01.DBF’
由于非系统表空间在db_block_checksum参数设为FALSE时,会忽略checksum的检查。所以这里为了测试的方便设置为TRUE。
从上面的错误信息来看,块号1447这个块已经坏了,报的错误是经典的ORA-01578错误。
我们用dbv检查一下这个文件:
D:\oracle\oradata\XJ>dbv file=TEST01.dbf blocksize=2048
DBVERIFY: Release 9.2.0.1.0 - Production on 星期一 2月 23 17:20:43 2009
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
DBVERIFY - 验证正在开始 : FILE = TEST01.dbf
标记为损坏的页1447
***
Corrupt block relative dba: 0×028005a7 (file 10, block 1447)
Bad check value found during dbv:
Data in bad block -
type: 6 format: 2 rdba: 0×028005a7
last change scn: 0×0000.0023b43e seq: 0×2 flg: 0×04
consistency value in tail: 0xb43e0602
check value in block header: 0xf0f0, computed block checksum: 0×3a4f
spare1: 0×0, spare2: 0×0, spare3: 0×0
***DBVERIFY-验证完成
检查的页总数:56660
处理的页总数(数据):53947
失败的页总数(数据):0
处理的页总数(索引):30
失败的页总数(索引):0
处理的页总数(其它):2669
处理的总页数(段):0
失败的总页数(段):0
空的页总数:13
标记为损坏的总页数:1
汇入的页总数:0
dbv检查发现了坏块(check错误)。
而如果用analyze命令检查也会发现有坏块:
SQL> analyze table test.t1 validate structure;
analyze table test.t1 validate structure
*
ERROR 位于第 1 行:
ORA-01578: ORACLE 数据块损坏(文件号10,块号1447)
ORA-01110: 数据文件 10: ‘D:\ORACLE\ORADATA\XJ\TEST01.DBF’
我们用dbms_repair来处理这个坏块(实际上如果只是checksum坏了,可以修改checksum为正确的值。但实际情况下,checksum坏了往往意味着坏内的数据已经坏了,大多数情况下只能丢弃):
SQL>>begin
2dbms_repair.admin_tables(
3table_name=>’REPAIR_TABLE’,
4table_type=>dbms_repair.repair_table,
5action=>dbms_repair.create_action,
6tablespace=>’SYSTEM’);
7end;
8/PL/SQL 过程已成功完成。
SQL> set serveroutput on
SQL>declare
2rpr_countint;
3begin
4rpr_count:=0;
5dbms_repair.check_object(
6schema_name=>’TEST’,
7object_name=>’T1′,
8repair_table_name=>’REPAIR_TABLE’,
9corrupt_count=>rpr_count);
10dbms_output.put_line(’repaircount:’||to_char(rpr_count));
11end;
12/
repair count: 1PL/SQL 过程已成功完成。
SQL> select object_name, block_id, corrupt_type, marked_corrupt,corrupt_description,
2 repair_description from repair_table;OBJECT_NAMEBLOCK_IDCORRUPT_TYPEMARKED_CORCORRUPT_DESCRIPTIONREPAIR_DESCRIPTION
-------------------------------------------------------------------------------------
T114476148TRUEmarkblocksoftware
corruptT114476148TRUEmarkblocksoftware
corrupt
SQL>declare
2fix_countint;
3begin
4fix_count:=0;
5dbms_repair.fix_corrupt_blocks(
6schema_name=>’TEST’,
7object_name=>’T1′,
8object_type=>dbms_repair.table_object,
9repair_table_name=>’REPAIR_TABLE’,
10fix_count=>fix_count);
11dbms_output.put_line(’fixcount:’||to_char(fix_count));
12end;
13/
fixcount:0PL/SQL过程已成功完成。
SQL>begin
2dbms_repair.skip_corrupt_blocks(
3schema_name=>’TEST’,
4object_name=>’T1′,
5object_type=>dbms_repair.table_object,
6flags=>dbms_repair.skip_flag);
7end;
8/PL/SQL过程已成功完成。
SQL>selecttable_name,skip_corruptfromdba_tableswheretable_name=’T1′andowner=’TEST’;
TABLE_NAMESKIP_COR
--------------------------------------
T1ENABLEDSQL>selectcount(*)fromtest.t1;
COUNT(*)
----------
28762SQL> alter system checkpoint;
系统已更改。
从上面可以看到,dbms_repair.fix_corrupt_blocks并不修复checksum错误,也不做坏块标记。通过dbv和用ultraedit检查块头,没有发现任何变化。但是通过dbms_repair.skip_corrupt_blocks过程在数据字典中将表设置为跳过坏块,则在查询时会跳过该块。
如果用RMAN备份该文件,而后还原该文件后,则这个坏块的seq_kcbh则被设为0xff。而此时用dbv检查该文件则显示的错误信息则为:
DBVERIFY - 验证正在开始 : FILE = TEST01.dbf
DBV-00200: 块, dba 41944487, 已经标记为崩溃
DBVERIFY - 验证完成
检查的页总数 :56655
处理的页总数(数据):53948
失败的页总数(数据):0
处理的页总数(索引):30
失败的页总数(索引):0
处理的页总数(其它):2669
处理的总页数 (段) : 0
失败的总页数 (段) : 0
空的页总数 :8
标记为损坏的总页数:0
汇入的页总数 :0
注意这里“标记为损坏的总页数”跟前一次检查的不一样,这里为“0”。
注意,使用skip_corrupt_blocks只能使oracle跳过Oracle能够读出的块,而如果在操作系统层read调用就失败的,则不能跳过该过。甚至于该会话也可能会被中断。遇到这样的情况,使用dd命令或操作系统的copy(cp)命令都不能复制该文件,rman也不能备份该文件,遇到这样的问题,如果数据文件没有备份怎么办?
在前几天我们的一个客户就遇上了这样的问题,windows系统,2节点RAC,使用了OCFS,由于存储及硬盘出现问题,1个数据文件出现坏块,连操作系统都不能复制出该文件。这样的情况在前几个月也遇到过,不过那个系统是Linux系统下的RAC(难不成OCFS的问题?二者都用了OCFS)。由于存储出了问题,硬盘亮了黄灯,换盘之后故障仍然存在。需要紧急备份这个库,但是那个文件始终无法复制出来。
遇到这样的情况,写个脚本把数据插入到另一个表?然后exp出来?到现场发现,那个坏块所在的表,居然有200G以上。有没有更简单的方法?到了客户那里,我利用大约20多分钟的时间,写了个简单的程序来复制这个不能利用操作系统工具复制出来的文件。其原理就是以块为单位读取数据,写入一个新的文件中,遇到读不出来的块,就写个一坏块(seq_kcbh设为0xff,flag_kcbh设为0×04,checksum就随便写入一个值,其他全为0)到新文件中。这样就复制出来了文件,幸运的是,整个文件复制其坏块只有2个。经过测试该文件完全可用。
顺便打个广告,这个复制坏文件的功能已经集成到了ODU 2.5.0版本中。