数据库出现坏块在我干DBA的时候是常见的事情,处理各种各样坏块的案例可能经历过上百个。很多情况下坏块问题可通过数据库恢复来完成,像Oracle早就支持块级恢复,因此现在Oracle出坏块,只要有备份,通过块级恢复就可以了。如果有ADG那就更简单了,通过ADG的文件恢复就可以了。
今天介绍的一个方法不仅可以在Oracle数据库上使用,也可以用于其他数据库。当数据库缺少可用备份或者不支持块级恢复,一张表出现一个坏块要恢复整个数据库比较麻烦的场景。处理方法比较简单,就是跳过坏的数据,把其他数据写入一张临时性的表,然后将存在坏块的表rename成old表,再把临时性的表rename成生产表就可以了。下面我们看这个案例。
因为某种不可言状的问题,用户的某张十分重要的表出现了访问问题。访问这张表的应用报错ORA-08103。通过客户端去访问这张表也是报同样的错误。
在该表的某个extent中,高水位下有个BLOCK对应的type是错误的,当对该表进行扫描或者访问到某个记录的时候,访问到这种块就会出现ORA-8103报错,操作无法正常进行。如果存在这种形式的坏块,无法对表进行全表扫描,也无法将该表exp出来。
通过对该表的检查,发现坏块的rdba是:72/13283,检查结果如下:
这个坏块中共有13条记录,当访问到这13条记录的时候就会出现ORA-8103错误。遇到这种问题,简单地通过dbms_repair去修复坏块是不行的,因为ORA-8103说明在高水位下遇到了一个rdba上看不输于这个段的块。通过bbed修改rdba后再来做repair是可行的,不过对于DBA的要求比较高。实际上遇到这样的问题其实有一种十分通用的简单解决方法,这个方法不仅对Oracle数据库有效,对大多数开源国产数据库同样有效。该方案有以下几个步骤:
1)首先创建一张表结构完全一致的表,比如new_temp表。
2)创建一张存储损坏行行号的临时表
3)找到一个该表的包含所有行的索引,比如主键或者非空字段上的索引,只要是能包含所有数据行就可以了。如果不存在这样的索引,那么就会稍微麻烦一些,要找到一种能够按照行遍历整个表的方法。
4)编写一个存储过程或者JAVA/PYTHON等程序来完成数据导出。
set serveroutput on
declare
nrows number;
badrows number;
VYHDABH VARCHAR2(50);
VFGSBH VARCHAR2(50);
VJBH VARCHAR2(50);
begin
badrows:=0;
nrows:=0;
for i in (select /*+ index (tab1) */ rowid,FGSBH from 有故障的表 tab1) loop
begin
insert into newtb select *
from 有故障的表 where rowid=i.rowid;
if (mod(nrows,20000)=0) then
commit;
end if;
exception when others then
badrows:=badrows+1;
//找到所有的该表的索引,尽可能把能找到的列数据都找出来
//写入BADROWS表里,尽可能多地恢复数据
select /*+ index(a YH_DNB_IND_2) */ yhdabh,FGSBH,JBH
into VYHDABH,VFGSBH,VJBH
from ld_data.yh_dnb a
where rowid=i.rowid;
insert into bad_rows values (i.Rowid,VFGSBH,VYHDABH,VJBH);
commit;
end;
nrows:=nrows+1;
end loop;
dbms_output.put_line('Total rows: '||to_char(nrows)||' Bad rows: '||to_char(badrows));
Commit;
end;
/
5)将原表rename 为Old
6)将临时表rename为原表
7)给新表添加索引与授权
在上面的这个案例中,这个坏块中一共存在13条记录,把这十三条记录的用户档案号也都找到了,于是恢复后只需要补录这十三条记录,整个数据就恢复了。
这个方法可以使用于其他数据库上,特别是段页式存储结构的数据库,比如达梦、神通Oscar等。PostgreSQL数据库有一个参数:zero_damaged_pages,用它可以跳过坏块遍历某张表。通过这个把数据导出到一张新表里。不过这种方式把坏块中的行全部丢弃了,对于关键业务数据来说存在数据丢失的风险。为了尽可能多的导出数据,可以再辅以上面的方法,通过适当的索引扫描,把坏块中的数据行找出来,把恢复工作做得更加完善。