Oracle 數據壞塊的 N 種修復方式
前言
Oracle 數據庫的運行不可避免的會遇到各種各樣的錯誤,就比如數據表出現壞塊,此時,你這張表的數據就無法訪問了,有什么好的辦法可以恢復呢?
什么,你沒有遇到過?😱
😏 那就祝你不久的將來遇到,哈哈開個玩笑~ 玩歸玩,鬧歸鬧,經驗必須要老到!👍🏻
一、介紹
今天就給大家講講怎么處理數據表的壞塊情況!🎉
對于 Oracle 數據塊物理損壞 的情形,通常可以分為兩種情況:
有備份,通過 RMAN 恢復
無備份,通過 DBMS_REPAIR 修復
1、RMAN
有備份的情況下,這是很理想的情形,我們可以直接通過 RMAN 塊介質恢復(BLOCK MEDIA RECOVERY)功能來完成受損塊的恢復。
這里我是不建議恢復整個數據庫或者數據庫文件來修復這些少量受損的數據塊,有點浪費時間。
可參考官方文檔:Block Media Recovery with RMAN
2、DBMS_REPAIR
那如果沒有任何備份怎么辦? (PS:備份大于一切!)
我們可以使用 Oracle 自帶的 DBMS_REPAIR 包來實現修復。
📢 注意: 使用 DBMS_REPAIR 包來修復,并非完全恢復,而是標記壞塊,然后不對其進行訪問,這部分被標記的數據也就丟失了,這是無法避免的。
可參考MOS文檔:DBMS_REPAIR SCRIPT (Doc ID 556733.1)
二、實戰環境準備
1、環境安裝可參考我的一鍵安裝腳本:Oracle 數據庫一鍵安裝,從未如此簡單
- cd /Volumes/DBA/voracle/github/single_db
- vagrant up
- vagrant ssh
2、測試數據準備
創建表空間:
- create tablespace eason datafile '/oradata/orcl/eason.dbf' size 1g autoextend on;
創建用戶:
- create user eason identified by eason default tablespace eason;
- grant dba to eason;
創建測試表:
- create table hyj as select * from dba_objects;
創建表索引:
- create index i_hyj on hyj(object_id);
3、查看表相關信息
查看表段上的相關信息:
- select segment_name , header_file , header_block,blocks from dba_segments where segment_name ='HYJ';
查出包含行記錄的數據塊:
- select distinct dbms_rowid.rowid_block_number(rowid) from eason.hyj order by 1;
- DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
- ------------------------------------
- 1411
- 1412
- 1413
- ...
- ...
- ...
- 2665
- 2666
- 2667
- 1232 rows selected.
- select * from dba_extents where segment_name='HYJ';
📢 注意: 這里看到 HEADER_BLOCK 和 BLOCK_ID 不一致,其實一個 segment 的第一個區的第一個塊是 FIRST LEVEL BITMAP BLOCK,第二個塊是 SECOND LEVEL BITMAP BLOCK,這兩個塊是用來管理 free block 的,第三個塊是 PAGETABLE SEGMENT HEADER,這個塊才是 segment 里的 HEADER_BLOCK。
4、RMAN 備份
首先,我們先做一個全備份,用來演示 RMAN 修復壞塊!
- run {
- allocate channel c1 device type disk;
- allocate channel c2 device type disk;
- crosscheck backup;
- crosscheck archivelog all;
- sql"alter system switch logfile";
- delete noprompt expired backup;
- delete noprompt obsolete device type disk;
- backup database include current controlfile format '/backup/backlv_%d_%T_%t_%s_%p';
- backup archivelog all DELETE INPUT;
- release channel c1;
- release channel c2;
- }
5、模擬壞塊
破壞 1468、1688、2468 數據塊的內容:
- dd if=/dev/zero of=/oradata/orcl/eason.dbf bs=8192 conv=notrunc seek=1468 count=1
- dd if=/dev/zero of=/oradata/orcl/eason.dbf bs=8192 conv=notrunc seek=1688 count=1
- dd if=/dev/zero of=/oradata/orcl/eason.dbf bs=8192 conv=notrunc seek=2468 count=1
清除 buffer cache 的內容:
- alter system flush buffer_cache;
再次查詢表 hyj,此時查詢已經報錯,發現有壞塊:
- select * from eason.hyj;
當然,也可以使用 bbed 進行壞塊模擬!
6、壞塊檢查
下面再介紹幾種發現壞塊的方式:
(1)使用 DBV 檢查當前文件的壞塊:
- dbv file=/oradata/orcl/eason.dbf blocksize=8192;
使用 DBV檢查,同樣發現了壞塊!
(2)使用 rman 檢查數據庫壞塊:
- ## 檢查對應的數據文件
- backup check logical validate datafile 5;
- ## 檢查當前數據庫
- backup validate check logical database;
結合 V$DATABASE_BLOCK_CORRUPTION 視圖查看,更加方便:
- select * from V$DATABASE_BLOCK_CORRUPTION;
使用 RMAN 檢查后,同樣發現了壞塊!
(3)通過數據庫的告警日志也可以發現報錯:
(4)通過報錯信息快速查找對應的壞表,依次填寫數據文件 ID 5 和 壞塊 ID 1468:
- SELECT tablespace_name, segment_type, owner, segment_name
- FROM dba_extents
- WHERE file_id = &fileid
- and &blockid between block_id AND block_id + blocks - 1;
實驗環境準備完畢,下面開始實戰!
三、實戰演示
今天,我打算使用上述介紹的 2 種方式來演示!
1、RMAN 修復
由于我們之前已經備份了,因此直接使用備份來恢復壞塊:
- blockrecover datafile 5 block 1468;
- blockrecover datafile 5 block 1688,2468;
使用同樣的方式,依次修復壞塊 1688,2468,修復成功后,查詢已恢復正常!
再次檢查壞塊情況:
- backup validate check logical database;
- select * from V$DATABASE_BLOCK_CORRUPTION;
壞塊已經都被恢復,并且數據沒有丟失!
2、DBMS_REPAIR 修復
首先,依然使用 dd 先模擬壞塊:
- dd if=/dev/zero of=/oradata/orcl/eason.dbf bs=8192 conv=notrunc seek=3333 count=1
- dd if=/dev/zero of=/oradata/orcl/eason.dbf bs=8192 conv=notrunc seek=3368 count=1
- dd if=/dev/zero of=/oradata/orcl/eason.dbf bs=8192 conv=notrunc seek=4000 count=1
在沒有備份的前提下,我們就無法做到無損修復壞塊了,需要損失對應壞塊的數據。
(1)創建 repair 表,用于記錄需要被修復的表:
- begin
- dbms_repair.admin_tables (
- table_name => 'REPAIR_TABLE',
- table_type => dbms_repair.repair_table,
- action => dbms_repair.create_action,
- tablespace => 'USERS');
- end;
- /
(2)創建 Orphan Key 表,用于記錄在表塊損壞后那些孤立索引,也就是指向壞塊的那些索引 :
- begin
- dbms_repair.admin_tables (
- table_name => 'ORPHAN_KEY_TABLE',
- table_type => dbms_repair.orphan_table,
- action => dbms_repair.create_action,
- tablespace => 'USERS');
- end;
- /
(3)檢查壞塊,檢測對象上受損的情形,并返回受損塊數為 3:
- declare
- num_corrupt int;
- begin
- num_corrupt := 0;
- DBMS_REPAIR.CHECK_OBJECT (
- schema_name =>'EASON',
- object_name =>'HYJ',
- repair_table_name =>'REPAIR_TABLE',
- corrupt_count =>num_corrupt);
- dbms_output.put_line('number corrupt:' || to_char(num_corrupt));
- end;
- /
(4)查看受損的塊信息:
- select object_name, block_id, corrupt_type, marked_corrupt, repair_description from repair_table;
📢 注意: 這里 marked_corrupt 被標記為 TRUE,應該是系統在執行 CHECK_OBJECT 過程中自動完成了FIX_CORRUPT_BLOCKS。如果被標記為 FALSE,需要再運行 FIX_CORRUPT_BLOCKS 來完成壞塊的標記工作。
(5)修復被損壞的數據塊,這些被損壞的數據塊是在執行了 check_object 之后生成的:
- declare
- cc number;
- begin
- dbms_repair.fix_corrupt_blocks(schema_name => 'EASON',
- object_name => 'HYJ',
- fix_count => cc);
- dbms_output.put_line('Number of blocks fixed: ' || to_char(cc));
- end;
- /
標記了 0 個壞塊,說明 CHECK_OBJECT 完成了標記工作。
(6)使用 DUMP_ORPHAN_KEYS 過程將那些指向壞塊的索引鍵值填充到 ORPHAN_KEY_TABLE:
- declare
- cc number;
- begin
- dbms_repair.dump_orphan_keys
- (
- schema_name => 'EASON',
- object_name => 'I_HYJ',
- object_type => dbms_repair.index_object,
- repair_table_name => 'REPAIR_TABLE',
- orphan_table_name=> 'ORPHAN_KEY_TABLE',
- key_count => cc
- );
- dbms_output.put_line('Number of orphan keys: ' || to_char(cc));
- end;
- /
表明 202 條記錄被損壞丟失!
📢 注意: 此處一定要注意 object_name 是索引名,而不是 table_name,這里 dump 的是損壞的索引信息.如果表有多個索引,需要為每個索引執行 DUMP_ORPHAN_KEYS 操作。
(7)驗證對象是否可以查詢,下面的結果顯示依舊無法查詢:
- select count(*) from eason.hyj;
(8)跳過壞塊:
- BEGIN
- DBMS_REPAIR.SKIP_CORRUPT_BLOCKS (
- SCHEMA_NAME => 'EASON',
- OBJECT_NAME => 'HYJ',
- OBJECT_TYPE => dbms_repair.table_object,
- FLAGS => dbms_repair.skip_flag);
- END;
- /
📢 注意: 丟失 202 條記錄,丟失記錄的 rowid 可以在 ORPHAN_KEY_TABLE 表中找到。
(9)重建索引:
- alter index eason.I_HYJ rebuild;
(10)驗證結果
至此,表中數據可以順利被訪問!
由于壞塊并沒有消失,而是被標記跳過,因此還是可以查看壞塊:
- select * from V$DATABASE_BLOCK_CORRUPTION;
用這種方法可以找回部分數據,也可以找回建了索引的值,但是使用dbv再檢查表空間的數據文件時,仍然會顯示有損壞的數據塊。
這時需要把表的數據全部導出,再重建表或者表空間,然后再把找回的數據導入數據庫,推薦用 expdp/impdp 命令做,可以徹底消除 dbv 檢查到的壞塊。
寫在最后
備份大于一切,也是最后的防線,所以請大家一定要做好備份!886