技术开发 频道

使用DBMS_REPAIR包修复损坏数据块

  【IT168 技术文档】

  摘要:介绍Oracle的DBMS_REPAIR,通过实例说明如何利用DBMS_REPAIR包标识出坏块。并给出了自编的一个包,用来恢复表中坏块对应的索引中存储的数据。

  1 、DBMS_REPAIR包的使用

  Oracle提供的DBMS_REPAIR包可以发现、标识并修改数据文件中的坏块。

  DBMS_REPAIR包的工作原理比较简单——将检查到的坏块标注出来,使随后的dml操作跳过该块。同时,DBMS_REPAIR包还提供了几个过程,可以用来保存索引的键值(这些键值指向被标注为坏块的block)的过程,以及修复freelists和segment bitmap的过程。

  DBMS_REPAIR包不但可以检测出坏块,根据表被索引的情况,还可以用来在一定程度上恢复坏块中的数据。

  需要注意,DBMS_REPAIR包没有进行授权,默认情况下,只有sys用户可以执行。

  下面通过一个完整的例子来说明DBMS_REPAIR包的使用。

  第一步:构造测试环境

首先建立一个测试用表空间,由于需要用UltraEdit打开数据文件修改部分内容来模拟错误,因此数据文件要建的小一些。

SQL> CREATE TABLESPACE TEST DATAFILE 'E:\ORACLE\ORADATA\TEST\TEST.DBF' SIZE 1M 2 EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT MANUAL; 表空间已创建。 SQL> CREATE TABLE TEST (ID NUMBER, NAME VARCHAR2(30)) TABLESPACE TEST; 表已创建。 SQL> INSERT INTO TEST SELECT ROWNUM, OBJECT_NAME FROM DBA_OBJECTS; 已创建6232行。 SQL> COMMIT; 提交完成。 SQL> CREATE INDEX IND_TEST_ID ON TEST (ID); 索引已创建。 SQL> CREATE INDEX IND_TEST_NAME ON TEST (NAME); 索引已创建。

  为了确保数据库已经把刚才插入的数据写到数据文件中,现在重起数据库。

SQL> CONN /@TEST AS SYSDBA 已连接。 SQL> SHUTDOWN 数据库已经关闭。 已经卸载数据库。 ORACLE 例程已经关闭。 SQL> STARTUP ORACLE 例程已经启动。 Total System Global Area 89201304 bytes Fixed Size 453272 bytes Variable Size 62914560 bytes Database Buffers 25165824 bytes Redo Buffers 667648 bytes 数据库装载完毕。 数据库已经打开。

  第二步:模拟错误的产生

用UltraEdit打开数据文件,只要修改了数据文件中任意的一个位置,都会造成数据文件错误。但我们测试需要将错误发生位置定位在TEST表中。

SQL> CONN YANGTK/YANGTK@TEST 已连接。 SQL> SELECT SUBSTR(ROWID, 10, 6), ID, NAME FROM TEST WHERE ID = 123; SUBSTR(ROWID ID NAME ------------ ---------- ------------------------------ AAAAAG 123 ALL_REPCONFLICT

  如何在数据文件中找到TEST表的数据呢?可以通过ROWID来定位的记录在数据文件中的位置。任意选择一条记录(如上面ID = 123),取得它的ROWID,我们知道,ROWID中10~15位表示这条记录所在的BLOCK是数据文件的第几个BLOCK。

  A表示0,B为1,G表示6。这说明这条记录在数据文件的第六个block中。

SQL> SHOW PARAMETER DB_BLOCK_SIZE NAME TYPE VALUE ------------------------------------ ----------- --------------- db_block_size integer 16384

  BLOCK的大小是16k。

SQL> SELECT TO_CHAR(6*16384, 'XXXXXX') FROM DUAL; TO_CHAR ------- 18000 SQL> SELECT TO_CHAR(7*16384, 'XXXXXX') FROM DUAL; TO_CHAR ------- 1C000

  用UltraEdit打开数据文件,将文件定位18000h处(以二进制方式打开,如果没有用二进制打开,可以使用CTRL+H快捷键切换)。根据上面的计算,可以得出,我们要找到记录在18000h和1C000h之间。

  Number类型123在数据库存放方式为03C20218,03表示占有三位,C2表示最高位是百位,02表示最高位上是1,18表示低位上是23。(如果对Oracle的基本数据类型的存储格式感兴趣,可以参考我在论坛上的帖子)

  具体的数值可以通过下面的查询得到:

SQL> SELECT DUMP(123, 16) FROM DUAL; DUMP(123,16) -------------------- Typ=2 Len=3: c2,2,18

  下面使用UltraEdit的搜索功能,查找到03C20218,将其修改为03C20216,并保存。

  上面是通过Oracle的ROWID在文件中定位,这相对来说要复杂一些。下面可以直接使用UltraEdit的搜索功能达到相同的目的。

  根据上面的查询可以得到,ID = 123时,NAME的值是ALL_REPCONFLICT。

  下面用UltraEdit打开文件,使用CTRL+H方式切换到文本格式,直接查找ALL_REPCONFLICT字符串。找到后,CTRL+H切换回二进制格式。向前跳过一个长度字节(本例中为0F),就可以看到123的值03C20218,进行修改后,保存并退出。

SQL> SELECT * FROM TEST WHERE ID = 123; ID NAME ---------- ------------------------------ 123 ALL_REPCONFLICT

  这时候查询仍然可以得到正确结果,因为oracle使用了db_cache中的结果。为了让oracle“看”到修改,必须重起数据库。

SQL> CONN /@TEST AS SYSDBA 已连接。 SQL> SHUTDOWN 数据库已经关闭。 已经卸载数据库。 ORACLE 例程已经关闭。 SQL> STARTUP ORACLE 例程已经启动。 Total System Global Area 89201304 bytes Fixed Size 453272 bytes Variable Size 62914560 bytes Database Buffers 25165824 bytes Redo Buffers 667648 bytes 数据库装载完毕。 数据库已经打开。 SQL> CONN YANGTK/YANGTK@TEST 已连接。 SQL> SELECT * FROM TEST WHERE ID = 123; SELECT * FROM TEST WHERE ID = 123 * ERROR 位于第 1 行: ORA-01578: ORACLE 数据块损坏(文件号7,块号6) ORA-01110: 数据文件 7: 'E:\ORACLE\ORADATA\TEST\TEST.DBF'

  已经模拟成功了坏块,开始进入正题部分,使用DBMS_REPAIR表来处理坏块。

  第三步:使用DBMS_REPAIR包处理坏块。

1.建立REPAIR_TABLE和ORPHAN_KEY_TABLE表

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 => 'YANGTK'); 7 END; 8 / PL/SQL 过程已成功完成。 SQL> BEGIN 2 DBMS_REPAIR.ADMIN_TABLES ( 3 TABLE_NAME => 'ORPHAN_KEY_TABLE', 4 TABLE_TYPE => dbms_repair.orphan_table, 5 ACTION => dbms_repair.create_action, 6 TABLESPACE => 'YANGTK'); 7 END; 8 / PL/SQL 过程已成功完成。

  REPAIR_TABLE用来记录错误检查结果,ORPHAN_KEY_TABLE用来记录表坏块中记录在索引中的对应键值。

  这两个表的删除可以通过下列存储过程完成

BEGIN DBMS_REPAIR.ADMIN_TABLES ( TABLE_NAME => 'REPAIR_TABLE', TABLE_TYPE => dbms_repair.repair_table, ACTION => dbms_repair.drop_action); END; / BEGIN DBMS_REPAIR.ADMIN_TABLES ( TABLE_NAME => 'ORPHAN_KEY_TABLE', TABLE_TYPE => dbms_repair.orphan_table, ACTION => dbms_repair.drop_action); END; /

  2.使用CHECK_OBJECT过程检测坏块。

SQL> SET SERVEROUTPUT ON SQL> DECLARE 2 num_corrupt INT; 3 BEGIN 4 num_corrupt := 0; 5 DBMS_REPAIR.CHECK_OBJECT ( 6 SCHEMA_NAME => 'YANGTK', 7 OBJECT_NAME => 'TEST', 8 REPAIR_TABLE_NAME => 'REPAIR_TABLE', 9 CORRUPT_COUNT => num_corrupt); 10 DBMS_OUTPUT.PUT_LINE('number corrupt: ' || TO_CHAR (num_corrupt)); 11 END; 12 / number corrupt: 1 PL/SQL 过程已成功完成。 SQL> SELECT OBJECT_NAME, BLOCK_ID, CORRUPT_TYPE, MARKED_CORRUPT, 2 CORRUPT_DESCRIPTION, REPAIR_DESCRIPTION 3 FROM REPAIR_TABLE; OBJECT_NAM BLOCK_ID CORRUPT_TYPE MARKED_COR CORRUPT_DE REPAIR_DESCRIPTION ---------- ---------- ------------ ---------- ---------- ---------------------- TEST 6 6148 TRUE mark block software corrupt

  这里和Oracle文档上面有点出入,根据Oracle的文档执行完这一步操作MARKED_CORRUPT列的值是FALSE,只有执行了FIX_CORRUPT_BLOCKS过程才会使MARKED_CORRUPT列的值变为TRUE。怀疑Oracle在CHECK的同时,会自动进行FIX_CORRUPT_BLOCKS的操作。

SQL> DECLARE 2 num_fix INT; 3 BEGIN 4 num_fix := 0; 5 DBMS_REPAIR.FIX_CORRUPT_BLOCKS ( 6 SCHEMA_NAME => 'YANGTK', 7 OBJECT_NAME=> 'TEST', 8 OBJECT_TYPE => dbms_repair.table_object, 9 REPAIR_TABLE_NAME => 'REPAIR_TABLE', 10 FIX_COUNT=> num_fix); 11 DBMS_OUTPUT.PUT_LINE('num fix: ' || TO_CHAR(num_fix)); 12 END; 13 / num fix: 0 PL/SQL 过程已成功完成。

  果然,执行FIX_CORRUPT_BLOCKS过程发现FIX了0个坏块,Oracle已经在检查的时候自动标识了坏块,这一步操作可以省略不用执行。

  3.使用DUMP_ORPHAN_KEYS过程来保存坏块中的索引键值。

  这时还存在着一个潜在的问题。表出现了坏块,但是索引没有损坏,通过表扫描会出现错误,但是通过索引扫描,仍然可以返回结果,这会造成数据的不一致。

SQL> SELECT * FROM YANGTK.TEST WHERE ID = 123; SELECT * FROM YANGTK.TEST WHERE ID = 123 * ERROR 位于第 1 行: ORA-01578: ORACLE 数据块损坏(文件号7,块号6) ORA-01110: 数据文件 7: 'E:\ORACLE\ORADATA\TEST\TEST.DBF' SQL> SELECT ID FROM YANGTK.TEST WHERE ID = 123; ID ---------- 123

  通过使用DUMP_ORPHAN_KEYS过程来保存坏块中数据对应的索引键值,这样当执行完SKIP_CORRUPT_BLOCKS操作后,就可以重新建立索引了。

SQL> DECLARE 2 num_orphans INT; 3 BEGIN 4 num_orphans := 0; 5 DBMS_REPAIR.DUMP_ORPHAN_KEYS ( 6 SCHEMA_NAME => 'YANGTK', 7 OBJECT_NAME => 'IND_TEST_ID', 8 OBJECT_TYPE => dbms_repair.index_object, 9 REPAIR_TABLE_NAME => 'REPAIR_TABLE', 10 ORPHAN_TABLE_NAME=> 'ORPHAN_KEY_TABLE', 11 KEY_COUNT => num_orphans); 12 DBMS_OUTPUT.PUT_LINE('orphan key count: ' || TO_CHAR(num_orphans)); 13 END; 14 / orphan key count: 549 PL/SQL 过程已成功完成。 SQL> DECLARE 2 num_orphans INT; 3 BEGIN 4 num_orphans := 0; 5 DBMS_REPAIR.DUMP_ORPHAN_KEYS ( 6 SCHEMA_NAME => 'YANGTK', 7 OBJECT_NAME => 'IND_TEST_NAME', 8 OBJECT_TYPE => dbms_repair.index_object, 9 REPAIR_TABLE_NAME => 'REPAIR_TABLE', 10 ORPHAN_TABLE_NAME=> 'ORPHAN_KEY_TABLE', 11 KEY_COUNT => num_orphans); 12 DBMS_OUTPUT.PUT_LINE('orphan key count: ' || TO_CHAR(num_orphans)); 13 END; 14 / orphan key count: 549 PL/SQL 过程已成功完成。

  注意对每个索引都要执行DUMP_ORPHAN_KEYS过程。

  4.使用REBUILD_FREELISTS过程来修改FREELISTS。

  如果坏块发生在FREELIST列表中的中部,则FREELIST列表后面的块都无法访问。在这个例子中,模拟产生的错误的位置不在FREELIST中,因此可以跳过这一步骤,一般情况下,由于无法定位确定坏块的位置,则需要执行此过程。

SQL> BEGIN 2 DBMS_REPAIR.REBUILD_FREELISTS ( 3 SCHEMA_NAME => 'YANGTK', 4 OBJECT_NAME => 'TEST', 5 OBJECT_TYPE => dbms_repair.table_object); 6 END; 7 / PL/SQL 过程已成功完成。

  5.执行SKIP_CORRUPT_BLOCKS过程,使后续的DML操作跳过坏块

SQL> BEGIN 2 DBMS_REPAIR.SKIP_CORRUPT_BLOCKS ( 3 SCHEMA_NAME => 'YANGTK', 4 OBJECT_NAME => 'TEST', 5 OBJECT_TYPE => dbms_repair.table_object, 6 FLAGS => dbms_repair.skip_flag); 7 END; 8 / PL/SQL 过程已成功完成。 SQL> SELECT OWNER, TABLE_NAME, SKIP_CORRUPT FROM DBA_TABLES 2 WHERE OWNER = 'YANGTK'; OWNER TABLE_NAME SKIP_COR ---------------------------- ---------------------------- -------- YANGTK TEST ENABLED YANGTK TEST1 DISABLED YANGTK TEST_AAA DISABLED YANGTK TEST_PART DISABLED 已选择4行。

  6.重建索引

  由于数据和索引仍然存在不一致的问题,因此必须重建索引。

SQL> SELECT * FROM YANGTK.TEST WHERE ID = 123; 未选定行 SQL> SELECT ID FROM YANGTK.TEST WHERE ID = 123; ID ---------- 123 SQL> ALTER INDEX YANGTK.IND_TEST_ID REBUILD; 索引已更改。 SQL> SELECT ID FROM YANGTK.TEST WHERE ID = 123; ID ---------- 123

  注意:重建索引一定要先DROP INDEX,然后CREATE INDEX的方式。如果使用了REBUILD的方式,那么重建索引时的数据源是原来的索引,重建后仍然会保留着不一致的数据。

SQL> DROP INDEX YANGTK.IND_TEST_ID; 索引已丢弃。 SQL> DROP INDEX YANGTK.IND_TEST_NAME; 索引已丢弃。 SQL> CREATE INDEX YANGTK.IND_TEST_ID ON YANGTK.TEST(ID); 索引已创建。 SQL> CREATE INDEX YANGTK.IND_TEST_NAME ON YANGTK.TEST(NAME); 索引已创建。 SQL> SELECT ID FROM YANGTK.TEST WHERE ID = 123; 未选定行 SQL> SELECT MIN(ID) FROM YANGTK.TEST; MIN(ID) ---------- 550

  包含ID = 123的块已经别标识为坏块。现在可以看到,最小的ID是550,也就是说,这个坏块中包含了549条记录。

SQL> SELECT COUNT(*) FROM ORPHAN_KEY_TABLE; COUNT(*) ---------- 1098

  继续查询ORPHAN_KEY_TABLE表,可以发现,这些记录的索引(2个)已经被保存到了ORPHAN_KEY_TABLE表中。

0
相关文章