Oracle数据文件的坏块,可分为物理坏块和逻辑坏块。物理坏块(也可以称为介质坏块)指的是块格式本身是坏的,块内的数据没有任何意义。而逻辑坏块,指的是块内的数据在逻辑是存在问题。比如说索引块的索引值没有按从小到大排列。物理坏块一般是由于内存问题、OS问题、IO子系统问题和硬件引起,逻辑坏块一般是是由于Oracle Bug等原因引起。

Oracle数据文件的每个块,其块头为20字节。其定义如下:(来自于DSI401)

struct kcbh
{
    ub1 type_kcbh; /* block type */
    ub2 frmt_kcbh; 
    ub1 spare1_kcbh;
    ub1 spare2_kcbh;
    krdba rdba_kcbh; /* relative DBA */
    ub4 bas_kcbh; /* base of SCN */
    ub2 wrp_kcbh; /* wrap of SCN */
    ub1 seq_kcbh; /* sequence # of changes at the same scn */
    ub1 flg_kcbh; 
    ub2 chkval_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_FILE HEADER_BLOCK
----------- ------------
         10         1445

修改db_block_checksum参数值为TRUE,关闭数据库,我们用ultraedit修改10号文件的1447块的check sum(一个随便>0的数)及flag=0x04。然后再打开数据库。再执行下面的查询:

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: 0x028005a7 (file 10, block 1447)
Bad check value found during dbv:
Data in bad block -
type: 6 format: 2 rdba: 0x028005a7
last change scn: 0x0000.0023b43e seq: 0x2 flg: 0x04
consistency value in tail: 0xb43e0602
check value in block header: 0xf0f0, computed block checksum: 0x3a4f
spare1: 0x0, spare2: 0x0, spare3: 0x0
***

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
  2    dbms_repair.admin_tables (
  3      table_name => 'REPAIR_TABLE',
  4      table_type => dbms_repair.repair_table,
  5      action => dbms_repair.create_action,
  6      tablespace => 'SYSTEM');
  7  end;
  8  /

PL/SQL 过程已成功完成。
SQL> set serveroutput on
SQL> declare
  2    rpr_count int;
  3  begin
  4    rpr_count := 0;
  5    dbms_repair.check_object (
  6      schema_name => 'TEST',
  7      object_name => 'T1',
  8      repair_table_name => 'REPAIR_TABLE',
  9      corrupt_count => rpr_count);
 10    dbms_output.put_line('repair count: ' || to_char(rpr_count));
 11  end;
 12  /
repair count: 1

PL/SQL 过程已成功完成。
SQL> select object_name, block_id, corrupt_type, marked_corrupt,corrupt_description,
2 repair_description from repair_table;

OBJECT_NAME     BLOCK_ID CORRUPT_TYPE MARKED_COR CORRUPT_DESCRIPTION  REPAIR_DESCRIPTION
------------- ---------- ------------ ---------- -------------------- --------------------
T1                  1447         6148 TRUE                            mark block software
                                                                      corrupt

T1                  1447         6148 TRUE                            mark block software
                                                                      corrupt
SQL> declare
  2    fix_count int;
  3    begin
  4      fix_count := 0;
  5      dbms_repair.fix_corrupt_blocks (
  6      schema_name => 'TEST',
  7      object_name => 'T1',
  8      object_type => dbms_repair.table_object,
  9      repair_table_name => 'REPAIR_TABLE',
 10      fix_count => fix_count);
 11    dbms_output.put_line('fix count: ' || to_char(fix_count));
 12  end;
 13  /
fix count: 0

PL/SQL 过程已成功完成。
SQL> begin
  2    dbms_repair.skip_corrupt_blocks (
  3      schema_name => 'TEST',
  4      object_name => 'T1',
  5      object_type => dbms_repair.table_object,
  6      flags => dbms_repair.skip_flag);
  7  end;
  8  /

PL/SQL 过程已成功完成。

SQL> select table_name, skip_corrupt from dba_tables where table_name = 'T1' and owner='TEST';

TABLE_NAME                     SKIP_COR
------------------------------ --------
T1                             ENABLED

SQL> select count(*) from test.t1;

  COUNT(*)
----------
     28762

SQL> 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设为0x04,checksum就随便写入一个值,其他全为0)到新文件中。这样就复制出来了文件,幸运的是,整个文件复制其坏块只有2个。经过测试该文件完全可用。

顺便打个广告,这个复制坏文件的功能已经集成到了ODU 2.5.0版本中。

,
Trackback

13 comments untill now

  1. 太牛了!

    [回复]

  2. 老熊真牛!
    “我们用ultraedit修改10号文件的1447块的check sum”——呃,这个怎么去找1477的块?UE打开数据文件后是乱码,怎么去找1477的块呢?

    [回复]

  3. to 小荷:
    就是 块号*块大小
    然后在UE中按CTRL+G,输入这个 块号*块大小的结果就可以了

    [回复]

  4. 希望能看到 写了个简单的程序来复制这个不能利用操作系统工具复制出来的文件 .

    [回复]

  5. to 小荷:
    就是 块号*块大小
    然后在UE中按CTRL+G,输入这个 块号*块大小的结果就可以了

    块号*块大小?那如何区分是哪个文件呢?比如块号是1447,数据文件1和2,甚至3都有可能有1447的块啊?

    [回复]

  6. 这里我省略了步骤。我把表T1建在TEST用户下,然后通过DBA_SEGMENT查到的是哪个文件和段头。当时段头是在10号文件的1445块。

    [回复]

  7. to qsxing:
    这是复制坏文件的一段关键代码:
         if ((fp=fopen64(cmd_line[4],”wb”))==NULL){
         sp_printf(“can not open file ‘%s’ for writing.\n”,cmd_line[4]);
         return;
         }

         filling=get_buffer(datafile_item->block_size,1);
     filling[14]=0xff; /*corrupt block*/
     filling[15]=0x04; /*has checksum*/
     set_ub2(filling,16,0xf0f0); /*check sum*/

       memset(filling,0,datafile_item->block_size);
              
       buffer=get_buffer(datafile_item->block_size,1);

       for (block_no=0;block_no< =datafile_item->blocks;block_no++){
         ret=read_blocks(datafile_item,block_no,1,buffer,block_no);
         if (ret!=0){
         fwrite(buffer,datafile_item->block_size,1,fp);
         continue;
         }
     sp_printf(“read block# error: %u\n”,block_no);
     fwrite(filling,datafile_item->block_size,1,fp);
         }

     free(buffer);
     free(filling);
     fclose(fp);

    [回复]

  8. 能介绍一下UE通过CTRL+G找到相应的块的原理,
    为什么CTRL+C不行,

    [回复]

    老熊 回复:

    @p, CTRL+G弹出的输入框中输入块头的起始位置就可以了,位置的计算公式很简单:块号*块大小。

    [回复]

  9. file 11,block 为5 ,公式意思是说:5*8192就可以么!

    [回复]

    老熊 回复:

    @p, 是的。

    [回复]

  10. 老熊,目前我这11g的库,系统表空间有个index坏了,我也没有备份。我能直接把index干掉,然后再建吗?

    [回复]

    老熊 回复:

    @wiii-gb, 系统表空间里面有的index可以直接重建,有的需要特殊手段重建,有的重建不了,要看具体是哪个表的哪个索引坏了。

    [回复]

Add your comment now