1、模拟坏块
SQL> create tablespace test01 datafile 'G:oracleproduct10.2.0oradataora10gtest01.dbf' size 1m;
表
空间已创建
。SQL> create table test(id number,name varchar2(30)) tablespace test01;
表已创建
。SQL> insert into test select rownum,object_name from dba_objects;
insert into test select rownum,object_name from dba_objects
*
第 1 行出现错误:
ORA-01653: 表 SYS.TEST 无法通过 8 (在表
空间 TEST01 中) 扩展
SQL> insert into test select rownum,object_name from dba_objects where rownum<10000;
已创建9999行。
SQL> commit;
提交完成。
SQL> insert into test select rownum,object_name from dba_objects where rownum<10000;
已创建9999行。
SQL> commit;
提交完成。
SQL> select count(*) from test;
COUNT(*)
----------
19998
SQL> create index id_inx on test(id);
索引已创建。
SQL> create index name_inx on test(name);
索引已创建。
SQL> shutdown immediate
数据库已经关闭。
已经卸载数据库。
ORACLE 例程已经关闭。
然后使用UltraEdit等工具编辑数据文件
,修改里面的部分内容。
SQL> startup
ORACLE 例程已经启动。
数据库装载完毕。
数据库已经打开。
SQL> select count(*) from test;
select count(*) from test
*
第 1 行出现错误:
ORA-01578: ORACLE 数据块损坏 (文件号 7, 块号 19)
ORA-01110: 数据文件 7: 'G:ORACLEPRODUCT10.2.0ORADATAORA10GTEST01.DBF'
可见我们已经模拟出坏块的情景。
2、使用analyze table 或DBV检测坏块的信息
SQL> analyze table test validate structure;
analyze table test validate structure
*
第 1 行出现错误:
ORA-01578: ORACLE 数据块损坏 (文件号 7, 块号 19)
ORA-01110: 数据文件 7: 'G:ORACLEPRODUCT10.2.0ORADATAORA10GTEST01.DBF'
通过对表进行analyze之后
,该表的相关坏块信息会写入到跟踪文件中,我们可以通过脚本获得此跟踪文件:
SQL> @G:oracleget_trace.sql;
TRACE_FILE
--------------------------------------------------------------------
G:ORACLEPRODUCT10.2.0ADMINORA10GUDUMPora10g_ora_3528.trc
查看ora10g_ora_3528.trc,下面是其中的部分信息:
Corrupt block relative dba: 0x01c00014 (file 7, block 20)
...
Corrupt block relative dba: 0x01c00013 (file 7, block 19)
...
跟踪文件中提示了数据文件7块19、20出现了坏块,下面使用DBV工具进行检测:
使用DBV工具时,两个参数是必须的,一个是FILE,一个是BLOCKSIZE:
G:oracleproduct10.2.0oradataora10g>dbv file=test01.dbf blocksize=8192
DBVERIFY - 开始验证: FILE = test01.dbf
页 19 标记为损坏
Corrupt block relative dba: 0x01c00013 (file 7, block 19)
...
页 20 标记为损坏
Corrupt block relative dba: 0x01c00014 (file 7, block 20)
...
页 23 标记为损坏
Corrupt block relative dba: 0x01c00017 (file 7, block 23)
...
页 31 标记为损坏
Corrupt block relative dba: 0x01c0001f (file 7, block 31)
...
DBVERIFY - 验证完成
检查的页总数: 128
处理的页总数 (数据): 106
失败的页总数 (数据): 0
处理的页总数 (索引): 0
失败的页总数 (索引): 0
处理的页总数 (其它): 18
处理的总页数 (段) : 0
失败的总页数 (段) : 0
空的页总数: 0
标记为损坏的总页数: 4
流入的页总数: 0
最高块 SCN : 1286361 (0.1286361)
我们可以见到,使用DBV工具检测出了该对象存在了4个坏块,而上面我们使用analyze命令时只检测出部分的坏块。
3、使用dbms_repair包进行坏块处理
1)首先建立repair_table,用于存放dbms_repair.check_object检测出来的坏块信息
SQL> declare
2 begin
3 dbms_repair.admin_tables
4 (table_name => 'REPAIR_TABLE',--表名
5 table_type => dbms_repair.repair_table,
6 action => dbms_repair.create_action,
7 tablespace => 'USERS');--用于指定该表存放的表空间
8 end;
9 /
PL/SQL 过程已成功完成。
SQL> col owner format a10
SQL> col object_name format a20
SQL> col object_type format a20
SQL> select owner, object_name, object_type
2 from dba_objects
3 where object_name like '%REPAIR_TABLE';
OWNER OBJECT_NAME OBJECT_TYPE
---------- -------------------- --------------------
SYS REPAIR_TABLE TABLE
SYS DBA_REPAIR_TABLE VIEW
Oracle自动创建了一个DBA_REPAIR_TABLE视图。
2)使用dbms_repair.check_object进行坏块检测
SQL> set serveroutput on size 100000;
SQL> declare
2 rpr_count int;
3 begin
4 rpr_count := 0;
5 dbms_repair.check_object(
6 schema_name => 'SYS',--指定对象模式,也就是对象的所有者
7 object_name => 'TEST',--指定对象名,也就是表名
8 repair_table_name => 'REPAIR_TABLE',
9 corrupt_count => rpr_count);
10 dbms_output.put_line('repair block count: '
11 ||to_char(rpr_count));
12 end;
13 /
repair block count: 4
PL/SQL 过程已成功完成。
SQL> select object_name, block_id, corrupt_type, marked_corrupt,
2 corrupt_description, repair_description
3 from repair_table;
OBJECT_NAME BLOCK_ID CORRUPT_TYPE MARKED_COR
-------------------- ---------- ------------ ----------
CORRUPT_DESCRIPTION
-------------------------------------------------------------------------------
REPAIR_DESCRIPTION
-------------------------------------------------------------------------------
TEST 19 6148 TRUE
mark block software corrupt
TEST 20 6148 TRUE
mark block software corrupt
TEST 23 6148 TRUE
mark block software corrupt
TEST 31 6148 TRUE
mark block software corrupt
通过运行dbms_repair.check_object,将坏块信息存放到了repair_table表中,其中有个字段marked_corrupt,用于标识该块是否被标识为坏块,当被标识为true时,即该块被标识为坏块。其中这一步跟oracle文档中的描述有点进入,根据oracle文档,当执行完dbms_repair.check_object时,并不会进行坏块标识,也就是marked_corrupt列的值应该为false,而只有当执行dbms_repair.fix_corrupt_blocks过程后才会进行坏块标识。
3)使用dbms_repair.fix_corrupt_blocks进行坏块标识
SQL> declare
2 fix_block_count int;
3 begin
4 fix_block_count := 0;
5 dbms_repair.fix_corrupt_blocks (
6 schema_name => 'SYS',
7 object_name => 'TEST',
8 object_type => dbms_repair.table_object,
9 repair_table_name => 'REPAIR_TABLE',
10 fix_count => fix_block_count);
11 dbms_output.put_line('fix blocks count: ' ||
12 to_char(fix_block_count));
13 end;
14 /
fix blocks count: 0
PL/SQL 过程已成功完成。
我们可以见到到fix blocks count=0,即在上一步进行check_object时已经进行了坏块标识了,这一步其实可以省略。(不过没有测试过!)
SQL> select count(*) from test;
select count(*) from test
*
第 1 行出现错误:
ORA-01578: ORACLE 数据块损坏 (文件号 7, 块号 19)
ORA-01110: 数据文件 7: 'G:ORACLEPRODUCT10.2.0ORADATAORA10GTEST01.DBF'
此时进行查询仍然报错,因为我们只是将坏块进行了标识,当进行全表扫描的时候,仍然会查询到坏块而报错。
4)使用dbms_repair.dump_orphan_keys过程来保存坏块的索引键值,然后再执行skip_corrupt_blocks过程之后,我们才能重建索引,不然重建索引时新的索引仍然会引用坏块。首先要建立ORPHAN_KEY_TABLE,此表就是用来存放坏块的索引键值。
SQL> declare
2 begin
3 dbms_repair.admin_tables
4 (table_name => 'ORPHAN_KEY_TABLE',
5 table_type => dbms_repair.orphan_table,
6 action => dbms_repair.create_action,
7 tablespace => 'USERS');
8 end;
9 /
PL/SQL 过程已成功完成。
然后执行过程dbms_repair.dump_orphan_keys将坏块键值存放到上面所创建的表中:
SQL> declare
2 orph_count int;
3 begin
4 orph_count:= 0;
5 dbms_repair.dump_orphan_keys (
6 schema_name => 'SYS',
7 object_name => 'ID_INX',--索引的名字
8 object_type => dbms_repair.index_object,
9 repair_table_name => 'REPAIR_TABLE',--从这个表中获得坏块的信息
10 orphan_table_name => 'ORPHAN_KEY_TABLE',
11 key_count => orph_count);
12 dbms_output.put_line('orphan-index entries: ' || to_char(orph_count));
13 end;
14 /
orphan-index entries: 491
PL/SQL 过程已成功完成。
SQL> declare
2 orph_count int;
3 begin
4 orph_count:= 0;
5 dbms_repair.dump_orphan_keys (
6 schema_name => 'SYS',
7 object_name => 'NAME_INX',
8 object_type => dbms_repair.index_object,
9 repair_table_name => 'REPAIR_TABLE',
10 orphan_table_name => 'ORPHAN_KEY_TABLE',
11 key_count => orph_count);
12 dbms_output.put_line('orphan-index entries: ' || to_char(orph_count));
13 end;
14 /
orphan-index entries: 491
PL/SQL 过程已成功完成。
对每个索引都要进行dump_orphan_keys。
SQL> select index_name, count(*) from orphan_key_table
2 group by index_name;
INDEX_NAME COUNT(*)
------------------------------ ----------
ID_INX 491
NAME_INX 491
5)使用skip_corrupt_blocks,使查询或者DML时跳过坏块
SQL> declare
2 begin
3 dbms_repair.skip_corrupt_blocks (
4 schema_name => 'SYS',
5 object_name => 'TEST',
6 object_type => dbms_repair.table_object,
7 flags => dbms_repair.skip_flag);
8 end;
9 /
PL/SQL 过程已成功完成。
SQL> select table_name, skip_corrupt from dba_tables
2 where table_name = 'TEST';
TABLE_NAME SKIP_COR
------------------------------ --------
TEST ENABLED
6)使用dbms_repair.rebuild_freelists重建freelists,使得该块不再被放到freelists,当中,也就是该块将不会再被使用。
SQL> declare
2 begin
3 dbms_repair.rebuild_freelists (
4 schema_name => 'SYS',
5 object_name => 'TEST',
6 object_type => dbms_repair.table_object);
7 end;
8 /
declare
*
第 1 行出现错误:
ORA-10614: Operation not allowed on this segment
ORA-06512: 在 "SYS.DBMS_REPAIR", line 400
ORA-06512: 在 line 3
不过我们可以看到,对于SYS用户下面的对象好像不能进行此操作。
4、重建索引
SQL> select count(id) from test;
COUNT(ID)
----------
19998
SQL> select count(name) from test;
COUNT(NAME)
-----------
19998
SQL> select count(*) from test;
COUNT(*)
----------
19507
我们可以看到上面的三个查询,对于第1和第2个使用索引进行查询和不使用索引进行查询的结果是不一样的。下面我们使用rebuild试试。
SQL> alter index id_inx rebuild;
索引已更改。
SQL> alter index name_inx rebuild;
索引已更改。
SQL> select count(id) from test;
COUNT(ID)
----------
19998
SQL> select count(name) from test;
COUNT(NAME)
-----------
19998
SQL> select count(*) from test;
COUNT(*)
----------
19507
可以是不能通过rebuild来重建索引的。只能通过DROP然后再CREATE。
SQL> drop index id_inx;
索引已删除。
SQL> drop index name_inx;
索引已删除。
SQL> create index id_inx on test(id);
索引已创建。
SQL> create index name_inx on test(name);
索引已创建。
SQL> select count(id) from test;
COUNT(ID)
----------
19507
SQL> select count(name) from test;
COUNT(NAME)
-----------
19507
SQL> select count(*) from test;
COUNT(*)
----------
19507
到此该表已经可以正常使用了,但同时也丢失了一些数据,所以在使用dbms_repair进行恢复的时候要充分考虑到数据的重要性和恢复的后果。同时也应该考虑是否有其它别的恢复方法,不然贸贸然的行事最后可能得不偿失。