1.故障原因

 

  前段时间在几个医院发生几次"ora-8103 对象不存在"的错误,出现这种错误,很多情况都是bug,但也可能出现在以下几种情况:
   -- 执行某个sql语句时
   -- 错误出现在alertsid.LOG日志中,一般伴随IO错误.
   -- dbv工具检查发现.
   错误的本质原因包括:
   --对象在当前操作开始后,被其他用户删除.
   --数据块头记录的块类型不正确.比如 数据块的类型为6(Type=6),但块不是数据块。这种情况一般发生突然断电,存储故障等情况下。也是我们目前遇到的最多的情况。也就是说ora-8103,本质上可能是一个块损坏,而不是整个段都出现问题,最容易发生在操作系统或硬件故障的情况下,特别容易发生在扩展 extent、或者是格式化新块的时候。
   --存储数据字典中的data_object_id与块中存储的data_object_id不一致。一些操作可能改变对象的data_object_id,比如删除表、trace table、重建索引,move表等。
   --因为一些原因,可能引发一个extent被两个段使用的情况,可以使用如下脚本进行检查:
   本地管理表空间:
   oradebug setmypid
   execute dbms_space_admin.tablespace_verify('&tablespace_name')
   oradebug tracefile_name
   
assm表空间检查脚本:
   oradebug setmypid
   execute dbms_space_admin.assm_tablespace_verify      ('&tablespace_name',dbms_space_admin.TS_VERIFY_BITMAPS)
   oradebug tracefile_name

   如果存在不一致,dbms_space_admin将会生成一个trace文件。

   由于字典管理表空间(DMT)基本不使用了,在此不做介绍。可以参考Metalink文档:Note 136697.1。
   
   2.找到错误的对象
   如何找到错误的对象呢?如果是SQL语句引发的错误,错误的对象要么是表,要么是索引。对于表,我们可以运行analyze来验证:

    analyze table <table_name> validate structure;

   当然也可以使用一个全表扫描的操作,可以使用"FULL"提示字来强制查询语句走全表扫描。对索引也同样可以使用analyze命令:

   
    analyze index <index_name> validate structure;
    
   如果是因为对象类型错误引发的8103错误,可以使用event来跟踪这个错误:
    alter session set events '8103 trace name errorstack level 1';
   如果是通过dbv工具进行检测到的错误,dbv工具会直接报告错误的block id,通过block_id可查询到具体的对象。

   3. 解决错误

    
    如果是数据块错误,可以尝试下列的步骤:
    a. 清空数据库高速缓存,看看这个错误是否是由于内存问题引起的。10g可以使用下列语法:

    alter system flush buffer_cache;

    9i及以前的版本,需要使用event:

    alter session set events 'immediate trace name flush_cache level 1';

    在RAC系统中,需要在RAC中的每个节点都进行清空高速缓存的操作。清空高速缓存,会严重影响系统性能,因为所有的数据块需要重新载入到内存中,这个操作是非常昂贵的。一般建议错开高峰执行。

    b.如果损坏的对象是索引,就比较好办了,直接删除重建这个索引即可。

    c.如果损坏泊是表,就比较麻烦了。如果数据是一些没有变化的字典表(比如,部门表、人员表这些),可以先truncate表,再使用imp工具从备份中导入。或者使用物理备份做介质恢复。RMAN的块恢复功能也可用于修复部分8103错误。
    如果从备份恢复不是可行的(没有备份,或是恢复代价太大),可以"跳过"引发8103错误的的数据块,oracle提供了一个参考的脚本:
    
    
    --创建一个用来保存错误rowid的表
  create table bad_rows (row_id rowid);
  set serveroutput on
  
  declare
  nrows number; 
  badrows number;
  begin
   badrows:=0;
   nrows:=0;
   --使用索引构建一个循环,因为索引保存了表中所有行的rowid,注意这里使用了index提示字来强制索引
   for i in (select /*+ index (tab1) */ rowid, <indexed column name> from <original table name> tab1) loop
    begin
     --将"好的"数据,insert into到新表中,通过rowid读取每一行
     insert into <new table name> select 
     <list of columns from table (ie col1, col2,..)>
     from <orig table name> where rowid=i.rowid;
  
     if (mod(nrows,10000)=0) then commit; end if;
     
    --如果发生错误,则insert错误的rowid到临时表中
    exception when others then
     badrows:=badrows+1;
     insert into bad_rows values (i.rowid);
     commit;
    end;
    nrows:=nrows+1;
   end loop;
   dbms_output.put_line('Total rows: '||to_char(nrows)||' Bad rows: '||to_char(badrows));
  end;
  /
   
      脚本中的关键是使用索引来获取行中每一行的rowid,通过索引中保存的rowid,来取得完整的行。
  还有一个方法就是使用dbms_repair表来跳过损坏的数据块,方法与ORA-01578,的处理方法相同。