oracle 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 过程已成功完成。   ---------数据恢复 oracle数据库恢复专家 13352468096 QQ:9417901 网站:http://www.sosdb.com-----
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表中。  
2 恢复数据  
使用DBMS_REPAIR包的目的不仅是为了使表重新可以访问,而且使用这个包还能在一定程度上恢复被因坏块而无法读取的数据。  
由于坏块产生在表上,因此索引是可以访问,所有被索引的列的数据都可以恢复。遗憾的是,Oracle的文档并没有给出恢复的方法,我查询了METALINK和ASKTOM也没有找到相应的答案,所以,恢复的工作只能靠自己摸索进行。这部分的内容完全是建立在我对Oracle数据类型理解的基础上的,虽然我已经对我的程序进行过测试,但是由于没有文档可以参考,而且测试环境相对比较单一,因此,我并不能确保我提供的包和函数一定没有错误。如果想将这种方法应用的正式系统中,请首先做好备份工作。  
言归正传,在上面的步骤中,使用DUMP_ORPHAN_KEYS过程保存了坏块中的索引键值,下面就通过这些保存的键值来进行数据的恢复。  
先看一下ORPHAN_KEY_TABLE的表结构:  
SQL> DESC ORPHAN_KEY_TABLE  
名称 是否为空? 类型  
-------------------------------------- -------- --------------  
SCHEMA_NAME NOT NULL VARCHAR2(30)  
INDEX_NAME NOT NULL VARCHAR2(30)  
IPART_NAME VARCHAR2(30)  
INDEX_ID NOT NULL NUMBER  
TABLE_NAME NOT NULL VARCHAR2(30)  
PART_NAME VARCHAR2(30)  
TABLE_ID NOT NULL NUMBER  
KEYROWID NOT NULL ROWID  
KEY NOT NULL ROWID  
DUMP_TIMESTAMP NOT NULL DATE  
由于字段名基本上都是自解释的,这里就不再过多描述了,需要说明的是KEYROWID和KEY两个字段。  
KEYROWID存放的是该索引键值对应的表中的ROWID,而KEY保存的就是索引的键值。  
但是查询KEY的值发现,并非像想象中一样,存放的是1、2、3……或ALL_TABLES、ACCESS$……等值,而是以逻辑ROWID方式存放的。  
SQL> SELECT KEY FROM ORPHAN_KEY_TABLE WHERE INDEX_NAME = 'IND_TEST_ID' AND ROWNUM = 1;  
KEY  
---------------------------------------------------------------  
*BAAAAAACwQL+  
SQL> SELECT KEY FROM ORPHAN_KEY_TABLE WHERE INDEX_NAME = 'IND_TEST_NAME' AND ROWNUM = 1;  
KEY  
---------------------------------------------------------------  
*BAAAAAAHQUNDRVNTJP4  
如何将逻辑ROWID转化为NUMBER或VARCHAR2类型呢?Oracle的文档并没有找到相应的解决方法。  
于是抱着尝试的想法对这个字段DUMP一下。  
SQL> SELECT DUMP(KEY) FROM ORPHAN_KEY_TABLE WHERE INDEX_NAME = 'IND_TEST_ID' AND ROWNUM < 6;  
DUMP(KEY)  
----------------------------------------------------------------  
Typ=208 Len=10: 2,4,0,0,0,0,2,193,2,254  
Typ=208 Len=10: 2,4,0,0,0,0,2,193,3,254  
Typ=208 Len=10: 2,4,0,0,0,0,2,193,4,254  
Typ=208 Len=10: 2,4,0,0,0,0,2,193,5,254  
Typ=208 Len=10: 2,4,0,0,0,0,2,193,6,254  
这回看到希望了。还记得上面修改数据文件时123的编码吗?是不是和第一个查询中的结果很相似?  
2,4,0,0,0,0前几位是不变的,最后一位254也是不变的。中间的部分就是有意义的数字了。其中第一个2表示长度是2,193表示最高位是个位,2表示最高位上的值是1,也就是说,第一个键值是数字1。(可以参考我对逻辑ROWID的存储格式进行分析的帖子)  
SQL> SELECT DUMP(1) FROM DUAL;  
DUMP(1)  
------------------  
Typ=2 Len=2: 193,2  
Oracle把数据在文件中的存储格式保存在ROWID字段中了。根据这个假设,我们看看字符串是不是以同样方式存储的。  
SQL> SELECT DUMP(KEY) FROM ORPHAN_KEY_TABLE WHERE INDEX_NAME = 'IND_TEST_NAME' AND ROWNUM < 6;  
DUMP(KEY)  
----------------------------------------------------------------  
Typ=208 Len=15: 2,4,0,0,0,0,7,65,67,67,69,83,83,36,254  
Typ=208 Len=17: 2,4,0,0,0,0,9,65,71,71,88,77,76,73,77,80,254  
Typ=208 Len=23: 2,4,0,0,0,0,15,65,71,71,88,77,76,73,78,80,85,84,84,89,80,69,254  
Typ=208 Len=22: 2,4,0,0,0,0,14,65,76,76,95,65,76,76,95,84,65,66,76,69,83,254  
Typ=208 Len=17: 2,4,0,0,0,0,9,65,76,76,95,65,80,80,76,89,254  
显然7代表长度,后面跟着的明显就是ASCII编码。  
---------数据恢复 oracle数据库恢复专家 13352468096 QQ:9417901 网站:http://www.sosdb.com-----
SQL> SELECT CHR(65)||CHR(67)||CHR(67)||CHR(69)||CHR(83)||CHR(83)||CHR(36) FROM DUAL;  
CHR(65)  
-------  
ACCESS$  
知道这个规律,就可以着手进行恢复了。我写了一个包,用来恢复各种数据类型的数据。  
SQL> CREATE OR REPLACE PACKAGE P_DUMPROWID  
2 AS  
3 --内部函数,由于去掉DUMP结果中的开头描述部分  
4 FUNCTION F_GET_VALUE(P_STR IN VARCHAR2) RETURN VARCHAR2;  
5 PRAGMA RESTRICT_REFERENCES (F_GET_VALUE, WNDS, WNPS, RNDS, RNPS);  
6  
7 --整理DUMP结果中16进制数,一位数的情况前面补0,并过滤逗号  
8 FUNCTION F_ADD_PREFIX_ZERO (P_STR IN VARCHAR2, P_POSITION IN NUMBER) RETURN VARCHAR2;  
9 PRAGMA RESTRICT_REFERENCES (F_ADD_PREFIX_ZERO, WNDS, WNPS, RNDS, RNPS);  
10  
11 --根据输入ROWID、方案名、索引名和索引中列的位置,将ROWID中的列的值提取出来  
12 FUNCTION F_DUMP_FROM_ROWID  
13 (  
14 P_DUMP_ROWID IN UROWID,  
15 P_OWNER IN VARCHAR2,  
16 P_INDEX_NAME IN VARCHAR2,  
17 P_COLUMN_POSITION IN NUMBER DEFAULT 1  
18 )  
19 RETURN VARCHAR2;  
20 PRAGMA RESTRICT_REFERENCES (F_DUMP_FROM_ROWID, WNDS, WNPS);  
21  
22 END P_DUMPROWID;  
23 /  
程序包已创建。  
SQL> CREATE OR REPLACE PACKAGE BODY P_DUMPROWID AS  
2  
3 FUNCTION F_GET_VALUE(P_STR IN VARCHAR2) RETURN VARCHAR2 AS  
4 BEGIN  
5 RETURN (SUBSTR(P_STR, INSTR(P_STR, ':') + 2));  
6 END F_GET_VALUE;  
7  
8 FUNCTION F_ADD_PREFIX_ZERO (P_STR IN VARCHAR2, P_POSITION IN NUMBER) RETURN VARCHAR2  
9 AS  
10 V_STR VARCHAR2(30000) := P_STR;  
11 V_POSITION NUMBER := P_POSITION;  
12 V_STR_PART VARCHAR2(2);  
13 V_RETURN VARCHAR2(30000);  
14 BEGIN  
15 WHILE (V_POSITION != 0) LOOP  
16 V_STR_PART := SUBSTR(V_STR, 1, V_POSITION - 1);  
17 V_STR := SUBSTR(V_STR, V_POSITION + 1);  
18  
19 IF V_POSITION = 2 THEN  
20 V_RETURN := V_RETURN || '0' || V_STR_PART;  
21 ELSIF V_POSITION = 3 THEN  
22 V_RETURN := V_RETURN || V_STR_PART;  
23 ELSE  
24 RAISE_APPLICATION_ERROR(-20002, 'DUMP ERROR CHECK THE INPUT ROWID');  
25 END IF;  
26  
27 V_POSITION := INSTR(V_STR, ',');  
28 END LOOP;  
29 RETURN REPLACE(V_RETURN , ',');  
30 END F_ADD_PREFIX_ZERO;  
31  
32 FUNCTION F_DUMP_FROM_ROWID  
33 (  
34 P_DUMP_ROWID IN UROWID,  
35 P_OWNER IN VARCHAR2,  
36 P_INDEX_NAME IN VARCHAR2,  
37 P_COLUMN_POSITION IN NUMBER DEFAULT 1  
38 )  
39 RETURN VARCHAR2 AS  
40 V_COLUMN_TYPE DBA_TAB_COLUMNS.DATA_TYPE%TYPE;  
41  
42 V_LENGTH_STR VARCHAR2(10);  
43 V_LENGTH NUMBER DEFAULT 7;  
44 V_DUMP_ROWID VARCHAR2(30000);  
45  
46 V_DATE_STR VARCHAR2(100);  
47 TYPE T_DATE IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;  
48 V_DATE T_DATE;  
49  
50 BEGIN  
51  
52 --根据SCHEMA、INDEX_NAME和COLUMN_NAME得到COLUMN的数据类型  
53 SELECT T.DATA_TYPE  
54 INTO V_COLUMN_TYPE  
55 FROM DBA_IND_COLUMNS I, DBA_TAB_COLUMNS T  
56 WHERE I.TABLE_NAME = T.TABLE_NAME  
57 AND I.TABLE_OWNER = T.OWNER  
58 AND I.COLUMN_NAME = T.COLUMN_NAME  
59 AND I.TABLE_OWNER = P_OWNER  
60 AND I.INDEX_NAME = P_INDEX_NAME  
61 AND I.COLUMN_POSITION = P_COLUMN_POSITION;  
62  
63 --根据COLUMN在索引中的位置,循环找到这个COLUMN对应的ROWID  
64 FOR I IN 1..P_COLUMN_POSITION LOOP  
65  
66 --如果COLUMN的长度超过127,即表示长度的位超过7f,则开始用两位来存储长度,其中第一位以8开始。  
67 SELECT F_GET_VALUE(DUMP(P_DUMP_ROWID, 16, V_LENGTH, 1)) INTO V_LENGTH_STR FROM DUAL;  
68 IF SUBSTR(V_LENGTH_STR, 1, 1) = '8' THEN  
69 SELECT SUBSTR(F_GET_VALUE(DUMP(P_DUMP_ROWID, 16, V_LENGTH, 2)), 2) INTO V_LENGTH_STR FROM DUAL;  
70 V_LENGTH_STR := TO_CHAR(TO_NUMBER(REPLACE(V_LENGTH_STR, ','), 'XXXX'));  
71 SELECT F_GET_VALUE(DUMP(P_DUMP_ROWID, 16, V_LENGTH + 2, TO_NUMBER(V_LENGTH_STR))) INTO V_DUMP_ROWID  
72 FROM DUAL;  
73 V_LENGTH := V_LENGTH + TO_NUMBER(V_LENGTH_STR) + 2;  
74 ELSE  
75 V_LENGTH_STR := TO_CHAR(TO_NUMBER(V_LENGTH_STR, 'XXX'));  
76 SELECT F_GET_VALUE(DUMP(P_DUMP_ROWID, 16, V_LENGTH + 1, TO_NUMBER(V_LENGTH_STR))) INTO V_DUMP_ROWID  
77 FROM DUAL;  
78 V_LENGTH := V_LENGTH + TO_NUMBER(V_LENGTH_STR) + 1;  
79 END IF;  
80 END LOOP;  
81  
82 IF V_COLUMN_TYPE = 'VARCHAR2' OR V_COLUMN_TYPE = 'CHAR' THEN  
83  
84 V_DUMP_ROWID :=F_ADD_PREFIX_ZERO(V_DUMP_ROWID || ',', INSTR(V_DUMP_ROWID, ','));  
85  
86 RETURN(UTL_RAW.CAST_TO_VARCHAR2(V_DUMP_ROWID));  
87 ELSIF V_COLUMN_TYPE = 'NUMBER' THEN  
88  
89 V_DUMP_ROWID :=F_ADD_PREFIX_ZERO(V_DUMP_ROWID || ',', INSTR(V_DUMP_ROWID, ','));  
90  
91 RETURN(TO_CHAR(UTL_RAW.CAST_TO_NUMBER(V_DUMP_ROWID)));  
92 ELSIF V_COLUMN_TYPE = 'DATE' THEN  
93  
94 V_DUMP_ROWID := ',' || V_DUMP_ROWID || ',';  
95  
96 FOR I IN 1..7 LOOP  
97 V_DATE(I) := TO_NUMBER(SUBSTR(V_DUMP_ROWID, INSTR(V_DUMP_ROWID, ',', 1, I) + 1,  
98 INSTR(V_DUMP_ROWID, ',', 1, I + 1) - INSTR(V_DUMP_ROWID, ',', 1, I) - 1), 'XXX');  
99 END LOOP;  
100  
101 V_DATE(1) := V_DATE(1) - 100;  
102 V_DATE(2) := V_DATE(2) - 100;  
103  
104 IF ((V_DATE(1) < 0) OR (V_DATE(2) < 0)) THEN  
105 V_DATE_STR := '-' || LTRIM(TO_CHAR(ABS(V_DATE(1)), '00')) || LTRIM(TO_CHAR(ABS(V_DATE(2)), '00'));  
106 ELSE  
107 V_DATE_STR := LTRIM(TO_CHAR(ABS(V_DATE(1)), '00')) || LTRIM(TO_CHAR(ABS(V_DATE(2)),'00'));  
108 END IF;  
109  
110 V_DATE_STR := V_DATE_STR || '-' || TO_CHAR(V_DATE(3)) || '-' || TO_CHAR(V_DATE(4)) || ' ' ||  
111 TO_CHAR(V_DATE(5) - 1) || ':' || TO_CHAR(V_DATE(6) - 1) || ':' || TO_CHAR(V_DATE(7) - 1);  
112 RETURN (V_DATE_STR);  
113 ELSIF V_COLUMN_TYPE LIKE 'TIMESTAMP(_)' THEN  
114  
115 V_DUMP_ROWID := ',' || V_DUMP_ROWID || ',';  
116  
117 FOR I IN 1..11 LOOP  
118 V_DATE(I) := TO_NUMBER(SUBSTR(V_DUMP_ROWID, INSTR(V_DUMP_ROWID, ',', 1, I) + 1,  
119 INSTR(V_DUMP_ROWID, ',', 1, I + 1) - INSTR(V_DUMP_ROWID, ',', 1, I) - 1), 'XXX');  
120 END LOOP;  
121  
122 V_DATE(1) := V_DATE(1) - 100;  
123 V_DATE(2) := V_DATE(2) - 100;  
124  
125 IF ((V_DATE(1) < 0) OR (V_DATE(2) < 0)) THEN  
126 V_DATE_STR := '-' || LTRIM(TO_CHAR(ABS(V_DATE(1)), '00')) || LTRIM(TO_CHAR(ABS(V_DATE(2)), '00'));  
127 ELSE  
128 V_DATE_STR := LTRIM(TO_CHAR(ABS(V_DATE(1)), '00')) || LTRIM(TO_CHAR(ABS(V_DATE(2)),'00'));  
129 END IF;  
130  
131 V_DATE_STR := V_DATE_STR || '-' || TO_CHAR(V_DATE(3)) || '-' || TO_CHAR(V_DATE(4)) || ' ' ||  
132 TO_CHAR(V_DATE(5) - 1) || ':' || TO_CHAR(V_DATE(6) - 1) || ':' || TO_CHAR(V_DATE(7) - 1) || '.' ||  
133 SUBSTR(TO_CHAR(V_DATE(8) * POWER(256, 3) + V_DATE(9) * POWER(256, 2) + V_DATE(10) * 256 + V_DATE(11)),  
134 1, TO_NUMBER(SUBSTR(V_COLUMN_TYPE, 11, 1)));  
135 RETURN (V_DATE_STR);  
136 ELSIF V_COLUMN_TYPE = 'RAW' THEN  
137  
138 V_DUMP_ROWID :=F_ADD_PREFIX_ZERO(V_DUMP_ROWID || ',', INSTR(V_DUMP_ROWID, ','));  
139  
140 RETURN(V_DUMP_ROWID);  
141 ELSIF V_COLUMN_TYPE = 'ROWID' THEN  
142  
143 V_DUMP_ROWID :=F_ADD_PREFIX_ZERO(V_DUMP_ROWID || ',', INSTR(V_DUMP_ROWID, ','));  
144  
145 RETURN (DBMS_ROWID.ROWID_CREATE(  
146 1,  
147 TO_NUMBER(SUBSTR(V_DUMP_ROWID, 1, 8), 'XXXXXXXXXXX'),  
148 TRUNC(TO_NUMBER(SUBSTR(V_DUMP_ROWID, 9, 4), 'XXXXXX')/64),  
149 TO_NUMBER(MOD(TO_NUMBER(SUBSTR(V_DUMP_ROWID, 9, 4), 'XXXXXX'), 64) ||  
150 TO_NUMBER(SUBSTR(V_DUMP_ROWID, 13, 4), 'XXXXXXXXXXX')),  
151 TO_NUMBER(SUBSTR(V_DUMP_ROWID, 17, 4), 'XXXXXX')));  
152 ELSE  
153 RAISE_APPLICATION_ERROR(-20001, 'TYPE NOT VALID OR CAN'T TRANSALTE ' || V_COLUMN_TYPE || ' TYPE');  
154 END IF;  
155 EXCEPTION  
156 WHEN NO_DATA_FOUND THEN  
157 RAISE_APPLICATION_ERROR(-20003,  
158 'INVALID SCHEMA_NAME OR INVALID INDEX_NAME OR INVALID COLUMN_NAME OR INVALID COLUMN_POSITION');  
159 END F_DUMP_FROM_ROWID;  
160  
161 END P_DUMPROWID;  
162 /  
程序包主体已创建。  
这个包主要对外提供一个函数F_DUMP_FROM_ROWID,另外两个函数是为了包中内部调用的。这个函数的输入参数分别是逻辑ROWID,表所在的SCHEMA,索引名称和抽取的列在索引中的位置。前三个参数分别由ORPHAN_KEY_TABLE表的KEY、SCHEMA_NAME和INDEX_NAME三个字段的值提供,最后一个参数的默认值为1,对于单列索引或复合索引的第一个字段的抽取,可以省略这个参数。  
由于dbms_repair包一般是由DBA执行的,因此一般把这个包也建立在SYS帐户中,如果用普通用户建立,需要对DBA_TAB_COLUMNS和DBA_IND_COLUMNS的单独授权。下面测试一下函数的功能。  
SQL> SELECT P_DUMPROWID.F_DUMP_FROM_ROWID(KEY, SCHEMA_NAME, INDEX_NAME) DUMP FROM ORPHAN_KEY_TABLE  
2 WHERE INDEX_NAME = 'IND_TEST_ID' AND ROWNUM < 6;  
DUMP  
-------------------------------------------  
1  
2  
3  
4  
5  
SQL> SELECT P_DUMPROWID.F_DUMP_FROM_ROWID(KEY, SCHEMA_NAME, INDEX_NAME, 1) DUMP FROM ORPHAN_KEY_TABLE  
2 WHERE INDEX_NAME = 'IND_TEST_NAME' AND ROWNUM < 6;  
DUMP  
-------------------------------------------  
ACCESS$  
AGGXMLIMP  
AGGXMLINPUTTYPE  
ALL_ALL_TABLES  
ALL_APPLY  
好了,剩下的事情就简单了。我们将ORPHAN_KEY_TABLE表中的记录转变后,重新插入到TEST表中即可。  
SQL> INSERT INTO YANGTK.TEST (ID, NAME)  
2 SELECT  
3 P_DUMPROWID.F_DUMP_FROM_ROWID(A.KEY, A.SCHEMA_NAME, A.INDEX_NAME),  
4 P_DUMPROWID.F_DUMP_FROM_ROWID(B.KEY, B.SCHEMA_NAME, B.INDEX_NAME)  
5 FROM ORPHAN_KEY_TABLE A, ORPHAN_KEY_TABLE B  
6 WHERE A.KEYROWID = B.KEYROWID  
7 AND A.INDEX_NAME = 'IND_TEST_ID'  
8 AND B.INDEX_NAME = 'IND_TEST_NAME';  
已创建549行。  
SQL> SELECT * FROM YANGTK.TEST WHERE ID = 1;   ---------数据恢复 oracle数据库恢复专家 13352468096 QQ:9417901 网站:http://www.sosdb.com-----
ID NAME  
---------- ------------------------------  
1 ACCESS$  
SQL> SELECT * FROM YANGTK.TEST WHERE ID = 123;  
ID NAME  
---------- ------------------------------  
123 ALL_REPCONFLICT  
SQL> COMMIT;  
至此,已经成功的恢复了数据。  
3 使用P_DUMPROWID恢复数据的例子  
下面再简单说明一下这个程序包是如何恢复其他数据类型的。  
通过查询ORPHAN_KEY_TABLE可以发现,Oracle把索引的值保存在逻辑ROWID中,我这个包实现的其实就是将逻辑ROWID中的值还原为索引键值。下面的例子为了简单起见,通过建立索引组织表,然后使用这个包还原索引组织表的ROWID信息作为参考的例子。  
例1:对NUMBER类型、VARCHAR2类型和TIME类型的恢复,对复合索引的恢复。  
SQL> create table test_index (id number, name varchar2(30), time date,  
2 constraint pk_test_index primary key (id, name, time))  
3 organization index;  
表已创建。  
SQL> insert into test_index select rownum, object_name, created from user_objects;  
已创建64行。  
SQL> commit;  
提交完成。  
SQL> col dump format a30  
SQL> alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss';  
会话已更改。  
SQL> select id, p_dumprowid.f_dump_from_rowid(ROWID, 'YANGTK', 'PK_TEST_INDEX') dump  
2 from test_index where rownum < 5;  
ID DUMP  
---------- ------------------------------  
1 1  
2 2  
3 3  
4 4  
SQL> select name, p_dumprowid.f_dump_from_rowid(ROWID, 'YANGTK', 'PK_TEST_INDEX', 2) dump  
2 from test_index where rownum < 5;  
NAME DUMP  
------------------------------ ------------------------------  
AA AA  
IND_Q1_ID IND_Q1_ID  
IND_Q1_ID IND_Q1_ID  
IND_Q1_ID IND_Q1_ID  
SQL> select time, p_dumprowid.f_dump_from_rowid(ROWID, 'YANGTK', 'PK_TEST_INDEX', 3) dump  
2 from test_index where rownum < 5;  
TIME DUMP  
------------------- ------------------------------  
2004-12-19 02:36:33 2004-12-19 2:36:33  
2004-12-18 23:17:56 2004-12-18 23:17:56  
2004-12-18 23:17:56 2004-12-18 23:17:56  
2004-12-18 23:18:33 2004-12-18 23:18:33  
例二:对TIMESTAMP类型的恢复。  
SQL> create table test_stamp (time timestamp constraint pk_test_stamp primary key)  
2 organization index;  
表已创建。  
SQL> insert into test_stamp values (systimestamp);  
已创建 1 行。  
SQL> alter session set nls_timestamp_format = 'yyyy-mm-dd hh24:mi:ss.ff';  
会话已更改。  
SQL> col dump format a40  
SQL> col time format a40  
SQL> select time, p_dumprowid.f_dump_from_rowid(rowid, 'YANGTK', 'PK_TEST_STAMP') dump  
2 from test_stamp;  
TIME DUMP  
---------------------------------------- ----------------------------------------  
2005-01-13 00:44:27.392000 2005-1-13 0:44:27.392000  
SQL> alter table test_stamp modify (time timestamp(9));  
表已更改。  
SQL> insert into test_stamp values (to_timestamp('2005-1-1 12:23:23.334282122',  
2 'yyyy-mm-dd hh24:mi:ss.ff'));  
已创建 1 行。  
SQL> select time, p_dumprowid.f_dump_from_rowid(rowid, 'YANGTK', 'PK_TEST_STAMP') dump  
2 from test_stamp;  
TIME DUMP  
---------------------------------------- ----------------------------------------  
2005-01-01 12:23:23.334282122 2005-1-1 12:23:23.334282122  
2005-01-13 00:44:27.392000000 2005-1-13 0:44:27.392000000  
这个包可以支持普通的单列BTREE索引和复合BTREE索引,不支持reverse key索引和BITMAP索引。  
目前支持的数据类型包括CHAR、VARCHAR2、NUMBER、DATE、TIMESTAMP、RAW和物理ROWID类型。这个包不支持的类型包括NCHAR、NVARCHAR2、TIMESTAMP WITH LOCAL TIME ZONE和TIMESTAMP WITH TIME ZONE等几种不很常用的类型(LONG、LONG RAW、逻辑ROWID和LOB类型不支持索引)。  
如果VARCHAR2(CHAR)类型中包含中文,在ZHS16GBK字符集下我测试通过,其他字符集没有测试,但估计对于一般中文字符集都不会有问题,但是如果用单字节字符集表示中文可能会有问题。  
任何一个工具也不可能只有好的方面而没有任何缺点。使用DBMS_REPAIR包的同时会带来数据丢失、表和索引返回数据不一致,完整性约束破坏等问题。因此当出现错误时,首先应当考虑用物理备份或逻辑备份进行恢复,DBMS_REPAIR包应该只是在没有备份的情况下使用的一种手段。DMBS_REPAIR包无法恢复表中没有被索引的列,因此使用这种方式一般都会造成数据的丢失。  
---------数据恢复 oracle数据库恢复专家 13352468096 QQ:9417901 网站:http://www.sosdb.com-----