Oracle 數(shù)據(jù)壞塊的 N 種修復(fù)方式
前言
Oracle 數(shù)據(jù)庫(kù)的運(yùn)行不可避免的會(huì)遇到各種各樣的錯(cuò)誤,就比如數(shù)據(jù)表出現(xiàn)壞塊,此時(shí),你這張表的數(shù)據(jù)就無(wú)法訪問(wèn)了,有什么好的辦法可以恢復(fù)呢?
什么,你沒(méi)有遇到過(guò)?😱
😏 那就祝你不久的將來(lái)遇到,哈哈開個(gè)玩笑~ 玩歸玩,鬧歸鬧,經(jīng)驗(yàn)必須要老到!👍🏻
一、介紹
今天就給大家講講怎么處理數(shù)據(jù)表的壞塊情況!🎉
對(duì)于 Oracle 數(shù)據(jù)塊物理?yè)p壞 的情形,通常可以分為兩種情況:
有備份,通過(guò) RMAN 恢復(fù)
無(wú)備份,通過(guò) DBMS_REPAIR 修復(fù)
1、RMAN
有備份的情況下,這是很理想的情形,我們可以直接通過(guò) RMAN 塊介質(zhì)恢復(fù)(BLOCK MEDIA RECOVERY)功能來(lái)完成受損塊的恢復(fù)。
這里我是不建議恢復(fù)整個(gè)數(shù)據(jù)庫(kù)或者數(shù)據(jù)庫(kù)文件來(lái)修復(fù)這些少量受損的數(shù)據(jù)塊,有點(diǎn)浪費(fèi)時(shí)間。
可參考官方文檔:Block Media Recovery with RMAN
2、DBMS_REPAIR
那如果沒(méi)有任何備份怎么辦? (PS:備份大于一切!)
我們可以使用 Oracle 自帶的 DBMS_REPAIR 包來(lái)實(shí)現(xiàn)修復(fù)。
📢 注意: 使用 DBMS_REPAIR 包來(lái)修復(fù),并非完全恢復(fù),而是標(biāo)記壞塊,然后不對(duì)其進(jìn)行訪問(wèn),這部分被標(biāo)記的數(shù)據(jù)也就丟失了,這是無(wú)法避免的。
可參考MOS文檔:DBMS_REPAIR SCRIPT (Doc ID 556733.1)
二、實(shí)戰(zhàn)環(huán)境準(zhǔn)備
1、環(huán)境安裝可參考我的一鍵安裝腳本:Oracle 數(shù)據(jù)庫(kù)一鍵安裝,從未如此簡(jiǎn)單
- cd /Volumes/DBA/voracle/github/single_db
- vagrant up
- vagrant ssh
2、測(cè)試數(shù)據(jù)準(zhǔn)備
創(chuàng)建表空間:
- create tablespace eason datafile '/oradata/orcl/eason.dbf' size 1g autoextend on;
創(chuàng)建用戶:
- create user eason identified by eason default tablespace eason;
- grant dba to eason;
創(chuàng)建測(cè)試表:
- create table hyj as select * from dba_objects;
創(chuàng)建表索引:
- create index i_hyj on hyj(object_id);
3、查看表相關(guān)信息
查看表段上的相關(guān)信息:
- select segment_name , header_file , header_block,blocks from dba_segments where segment_name ='HYJ';
查出包含行記錄的數(shù)據(jù)塊:
- 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 不一致,其實(shí)一個(gè) segment 的第一個(gè)區(qū)的第一個(gè)塊是 FIRST LEVEL BITMAP BLOCK,第二個(gè)塊是 SECOND LEVEL BITMAP BLOCK,這兩個(gè)塊是用來(lái)管理 free block 的,第三個(gè)塊是 PAGETABLE SEGMENT HEADER,這個(gè)塊才是 segment 里的 HEADER_BLOCK。
4、RMAN 備份
首先,我們先做一個(gè)全備份,用來(lái)演示 RMAN 修復(fù)壞塊!
- 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 數(shù)據(jù)塊的內(nèi)容:
- 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 的內(nèi)容:
- alter system flush buffer_cache;
再次查詢表 hyj,此時(shí)查詢已經(jīng)報(bào)錯(cuò),發(fā)現(xiàn)有壞塊:
- select * from eason.hyj;
當(dāng)然,也可以使用 bbed 進(jìn)行壞塊模擬!
6、壞塊檢查
下面再介紹幾種發(fā)現(xiàn)壞塊的方式:
(1)使用 DBV 檢查當(dāng)前文件的壞塊:
- dbv file=/oradata/orcl/eason.dbf blocksize=8192;
使用 DBV檢查,同樣發(fā)現(xiàn)了壞塊!
(2)使用 rman 檢查數(shù)據(jù)庫(kù)壞塊:
- ## 檢查對(duì)應(yīng)的數(shù)據(jù)文件
- backup check logical validate datafile 5;
- ## 檢查當(dāng)前數(shù)據(jù)庫(kù)
- backup validate check logical database;
結(jié)合 V$DATABASE_BLOCK_CORRUPTION 視圖查看,更加方便:
- select * from V$DATABASE_BLOCK_CORRUPTION;
使用 RMAN 檢查后,同樣發(fā)現(xiàn)了壞塊!
(3)通過(guò)數(shù)據(jù)庫(kù)的告警日志也可以發(fā)現(xiàn)報(bào)錯(cuò):
(4)通過(guò)報(bào)錯(cuò)信息快速查找對(duì)應(yīng)的壞表,依次填寫數(shù)據(jù)文件 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;
實(shí)驗(yàn)環(huán)境準(zhǔn)備完畢,下面開始實(shí)戰(zhàn)!
三、實(shí)戰(zhàn)演示
今天,我打算使用上述介紹的 2 種方式來(lái)演示!
1、RMAN 修復(fù)
由于我們之前已經(jīng)備份了,因此直接使用備份來(lái)恢復(fù)壞塊:
- blockrecover datafile 5 block 1468;
- blockrecover datafile 5 block 1688,2468;
使用同樣的方式,依次修復(fù)壞塊 1688,2468,修復(fù)成功后,查詢已恢復(fù)正常!
再次檢查壞塊情況:
- backup validate check logical database;
- select * from V$DATABASE_BLOCK_CORRUPTION;
壞塊已經(jīng)都被恢復(fù),并且數(shù)據(jù)沒(méi)有丟失!
2、DBMS_REPAIR 修復(fù)
首先,依然使用 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
在沒(méi)有備份的前提下,我們就無(wú)法做到無(wú)損修復(fù)壞塊了,需要損失對(duì)應(yīng)壞塊的數(shù)據(jù)。
(1)創(chuàng)建 repair 表,用于記錄需要被修復(fù)的表:
- begin
- dbms_repair.admin_tables (
- table_name => 'REPAIR_TABLE',
- table_type => dbms_repair.repair_table,
- action => dbms_repair.create_action,
- tablespace => 'USERS');
- end;
- /
(2)創(chuàng)建 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)檢查壞塊,檢測(cè)對(duì)象上受損的情形,并返回受損塊數(shù)為 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 被標(biāo)記為 TRUE,應(yīng)該是系統(tǒng)在執(zhí)行 CHECK_OBJECT 過(guò)程中自動(dòng)完成了FIX_CORRUPT_BLOCKS。如果被標(biāo)記為 FALSE,需要再運(yùn)行 FIX_CORRUPT_BLOCKS 來(lái)完成壞塊的標(biāo)記工作。
(5)修復(fù)被損壞的數(shù)據(jù)塊,這些被損壞的數(shù)據(jù)塊是在執(zhí)行了 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;
- /
標(biāo)記了 0 個(gè)壞塊,說(shuō)明 CHECK_OBJECT 完成了標(biāo)記工作。
(6)使用 DUMP_ORPHAN_KEYS 過(guò)程將那些指向壞塊的索引鍵值填充到 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 的是損壞的索引信息.如果表有多個(gè)索引,需要為每個(gè)索引執(zhí)行 DUMP_ORPHAN_KEYS 操作。
(7)驗(yàn)證對(duì)象是否可以查詢,下面的結(jié)果顯示依舊無(wú)法查詢:
- select count(*) from eason.hyj;
(8)跳過(guò)壞塊:
- 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)驗(yàn)證結(jié)果
至此,表中數(shù)據(jù)可以順利被訪問(wèn)!
由于壞塊并沒(méi)有消失,而是被標(biāo)記跳過(guò),因此還是可以查看壞塊:
- select * from V$DATABASE_BLOCK_CORRUPTION;
用這種方法可以找回部分?jǐn)?shù)據(jù),也可以找回建了索引的值,但是使用dbv再檢查表空間的數(shù)據(jù)文件時(shí),仍然會(huì)顯示有損壞的數(shù)據(jù)塊。
這時(shí)需要把表的數(shù)據(jù)全部導(dǎo)出,再重建表或者表空間,然后再把找回的數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫(kù),推薦用 expdp/impdp 命令做,可以徹底消除 dbv 檢查到的壞塊。
寫在最后
備份大于一切,也是最后的防線,所以請(qǐng)大家一定要做好備份!886