Oracle資料檔案的壞塊,可分為物理壞塊和邏輯壞塊。物理壞塊(也可以稱為介質壞塊)指的是塊格式本身是壞的,塊內的資料沒有任何意義。而邏輯壞塊,指的是塊內的資料在邏輯是存在問題。比如說索引塊的索引值沒有按從小到大排列。物理壞塊一般是由於記憶體問題、OS問題、IO子系統問題和硬體引起,邏輯壞塊一般是是由於Oracle Bug等原因引起。
Oracle資料檔案的每個塊,其塊頭為20位元組。其定義如下:(來自於DSI401)
struct kcbh
{
ub1 type_kcbh; /* block type */
ub2 frmt_kcbh;
ub1 spare1_kcbh;
ub1 spare2_kcbh;
krdba rdba_kcbh; /* relative DBA */
ub4 bas_kcbh; /* base of SCN */
ub2 wrp_kcbh; /* wrap of SCN */
ub1 seq_kcbh; /* sequence # of changes at the same scn */
ub1 flg_kcbh;
ub2 chkval_kcbh;
};
在塊頭中,seq_kcbh(佔用1位元組,塊頭位移14)有著特殊的含義,如果該值為0xff,則表示該塊被標記為corruption。
下面我們做一個測試:
SQL> create table test.t1 as select * from dba_objects;
表已建立。
SQL> select header_file,header_block from dba_segments where segment_name=’T1′ and owner=’TEST’;
HEADER_FILE HEADER_BLOCK
----------- ------------
10 1445
修改db_block_checksum參數值為TRUE,關閉資料庫,我們用ultraedit修改10號檔案的1447塊的check sum(一個隨便>0的數)及flag=0×04。然後再開啟資料庫。再執行下面的查詢:
SQL> select count(*) from test.t1;
select count(*) from test.t1
*
ERROR 位於第 1 行:
ORA-01578: ORACLE 資料區塊損壞(檔案號10,塊號1447)
ORA-01110: 資料檔案 10: ‘D:\ORACLE\ORADATA\XJ\TEST01.DBF’
由於非系統資料表空間在db_block_checksum參數設為FALSE時,會忽略checksum的檢查。所以這裡為了測試的方便設定為TRUE。
從上面的錯誤資訊來看,塊號1447這個塊已經壞了,報的錯誤是經典的ORA-01578錯誤。
我們用dbv檢查一下這個檔案:
D:\oracle\oradata\XJ>dbv file=TEST01.dbf blocksize=2048
DBVERIFY: Release 9.2.0.1.0 - Production on 星期一 2月 23 17:20:43 2009
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
DBVERIFY - 驗證正在開始 : FILE = TEST01.dbf
標記為損壞的頁1447
***
Corrupt block relative dba: 0×028005a7 (file 10, block 1447)
Bad check value found during dbv:
Data in bad block -
type: 6 format: 2 rdba: 0×028005a7
last change scn: 0×0000.0023b43e seq: 0×2 flg: 0×04
consistency value in tail: 0xb43e0602
check value in block header: 0xf0f0, computed block checksum: 0×3a4f
spare1: 0×0, spare2: 0×0, spare3: 0×0
***
DBVERIFY - 驗證完成
檢查的頁總數 :56660
處理的頁總數(資料):53947
失敗的頁總數(資料):0
處理的頁總數(索引):30
失敗的頁總數(索引):0
處理的頁總數(其它):2669
處理的總頁數 (段) : 0
失敗的總頁數 (段) : 0
空的頁總數 :13
標記為損壞的總頁數:1
匯入的頁總數 :0
dbv檢查發現了壞塊(check錯誤)。
而如果用analyze命令檢查也會發現有壞塊:
SQL> analyze table test.t1 validate structure;
analyze table test.t1 validate structure
*
ERROR 位於第 1 行:
ORA-01578: ORACLE 資料區塊損壞(檔案號10,塊號1447)
ORA-01110: 資料檔案 10: ‘D:\ORACLE\ORADATA\XJ\TEST01.DBF’
我們用dbms_repair來處理這個壞塊(實際上如果只是checksum壞了,可以修改checksum為正確的值。但實際情況下,checksum壞了往往意味著壞內的資料已經壞了,大多數情況下只能丟棄):
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 => ’SYSTEM’);
7 end;
8 /
PL/SQL 過程已成功完成。
SQL> set serveroutput on
SQL> declare
2 rpr_count int;
3 begin
4 rpr_count := 0;
5 dbms_repair.check_object (
6 schema_name => ’TEST’,
7 object_name => ’T1′,
8 repair_table_name => ’REPAIR_TABLE’,
9 corrupt_count => rpr_count);
10 dbms_output.put_line(’repair count: ’ || to_char(rpr_count));
11 end;
12 /
repair count: 1
PL/SQL 過程已成功完成。
SQL> select object_name, block_id, corrupt_type, marked_corrupt,corrupt_description,
2 repair_description from repair_table;
OBJECT_NAME BLOCK_ID CORRUPT_TYPE MARKED_COR CORRUPT_DESCRIPTION REPAIR_DESCRIPTION
------------- ---------- ------------ ---------- -------------------- --------------------
T1 1447 6148 TRUE mark block software
corrupt
T1 1447 6148 TRUE mark block software
corrupt
SQL> declare
2 fix_count int;
3 begin
4 fix_count := 0;
5 dbms_repair.fix_corrupt_blocks (
6 schema_name => ’TEST’,
7 object_name => ’T1′,
8 object_type => dbms_repair.table_object,
9 repair_table_name => ’REPAIR_TABLE’,
10 fix_count => fix_count);
11 dbms_output.put_line(’fix count: ’ || to_char(fix_count));
12 end;
13 /
fix count: 0
PL/SQL 過程已成功完成。
SQL> begin
2 dbms_repair.skip_corrupt_blocks (
3 schema_name => ’TEST’,
4 object_name => ’T1′,
5 object_type => dbms_repair.table_object,
6 flags => dbms_repair.skip_flag);
7 end;
8 /
PL/SQL 過程已成功完成。
SQL> select table_name, skip_corrupt from dba_tables where table_name = ’T1′ and owner=’TEST’;
TABLE_NAME SKIP_COR
------------------------------ --------
T1 ENABLED
SQL> select count(*) from test.t1;
COUNT(*)
----------
28762
SQL> alter system checkpoint;
系統已更改。
從上面可以看到,dbms_repair.fix_corrupt_blocks並不修複checksum錯誤,也不做壞塊標記。通過dbv和用ultraedit檢查塊頭,沒有發現任何變化。但是通過dbms_repair.skip_corrupt_blocks過程在資料字典中將表設定為跳過壞塊,則在查詢時會跳過該塊。
如果用RMAN備份該檔案,而後還原該檔案後,則這個壞塊的seq_kcbh則被設為0xff。而此時用dbv檢查該檔案則顯示的錯誤資訊則為:
DBVERIFY - 驗證正在開始 : FILE = TEST01.dbf
DBV-00200: 塊, dba 41944487, 已經標記為崩潰
DBVERIFY - 驗證完成
檢查的頁總數 :56655
處理的頁總數(資料):53948
失敗的頁總數(資料):0
處理的頁總數(索引):30
失敗的頁總數(索引):0
處理的頁總數(其它):2669
處理的總頁數 (段) : 0
失敗的總頁數 (段) : 0
空的頁總數 :8
標記為損壞的總頁數:0
匯入的頁總數 :0
注意這裡“標記為損壞的總頁數”跟前一次檢查的不一樣,這裡為“0”。
注意,使用skip_corrupt_blocks只能使oracle跳過Oracle能夠讀出的塊,而如果在作業系統層read調用就失敗的,則不能跳過該過。甚至於該會話也可能會被中斷。遇到這樣的情況,使用dd命令或作業系統的copy(cp)命令都不能複製該檔案,rman也不能備份該檔案,遇到這樣的問題,如果資料檔案沒有備份怎麼辦?
在前幾天我們的一個客戶就遇上了這樣的問題,windows系統,2節點RAC,使用了OCFS,由於儲存及硬碟出現問題,1個資料檔案出現壞塊,連作業系統都不能複製出該檔案。這樣的情況在前幾個月也遇到過,不過那個系統是Linux系統下的RAC(難不成OCFS的問題?二者都用了OCFS)。由於儲存出了問題,硬碟亮了黃燈,換盤之後故障仍然存在。需要緊急備份這個庫,但是那個檔案始終無法複製出來。
遇到這樣的情況,寫個指令碼把資料插入到另一個表?然後exp出來?到現場發現,那個壞塊所在的表,居然有200G以上。有沒有更簡單的方法?到了客戶那裡,我利用大約20多分鐘的時間,寫了個簡單的程式來複製這個不能利用作業系統工具複製出來的檔案。其原理就是以塊為單位讀取資料,寫入一個新的檔案中,遇到讀不出來的塊,就寫個一壞塊(seq_kcbh設為0xff,flag_kcbh設為0×04,checksum就隨便寫入一個值,其他全為0)到新檔案中。這樣就複製出來了檔案,幸運的是,整個檔案複製其壞塊只有2個。經過測試該檔案完全可用。