프린트 하기

OS 환경 : Oracle Linux 8.1 (64bit)

 

DB 환경 : Oracle Database 19.3.0.0

 

방법 : 오라클 19c block corrupt 발생시 dbms_repair skip 사용 가이드

오라클 19c 환경에서 테이블에 block corrupt 가 발생한 경우 dbms_repair를 이용해 해당 블록을 skip 해주어야 정상적으로 다시 읽을 수 있음
본문에서는 블록 커럽트를 유발하고 dbms_repair를 이용해 skip 하는 방법을 설명함
또한 skip 처리 후 인덱스 rebuild가 필요한데 rebuild를 안했을때는 어떻게 나오는지도 확인해봄

 

 

테스트
샘플 테이블스페이스 생성

1
2
3
SQL> create tablespace corrupt_ts1 datafile '/oradata1/ORACLE19/corrupt_tbs1.dbf' size 100m;
 
Tablespace created.

 

 

유저 없는 경우 생성
참고 : 오라클 19c 테스트 시 사용하는 imsi 유저 생성 쿼리 ( https://positivemh.tistory.com/1017 )

 

 

유저에 해당 테이블스페이스 권한 부여

1
2
3
SQL> alter user imsi quota unlimited on corrupt_ts1;
 
User altered.

 

 

샘플 테이블, 인덱스 생성

1
2
3
4
5
SQL>
conn imsi/imsi
drop table corrupt_test1 purge;
create table corrupt_test1(col1 number, col2 varchar2(10)) tablespace corrupt_ts1;
create index corrupt_test1_ix1 on corrupt_test1(col1) tablespace corrupt_ts1;

 

 

샘플 데이터 삽입

1
2
3
4
5
6
7
8
9
10
11
SQL>
begin
    for i in 1..1000 loop
        insert into corrupt_test1 (col1, col2)
        values (i, dbms_random.string('a', 10));
    end loop;
    commit;
end;
/
 
PL/SQL procedure successfully completed.

 

 

샘플 테이블 count 확인

1
2
3
4
5
SQL> select count(*) from corrupt_test1;
 
  COUNT(*)
----------
      1000

 

 

샘플 테이블의 전체 datafile number, block number 확인

1
2
3
4
5
6
7
8
9
10
SQL> 
select distinct dbms_rowid.rowid_relative_fno(rowid) fno, dbms_rowid.rowid_block_number(rowid) blk_number
from corrupt_test1
order by 1, 2;
 
       FNO BLK_NUMBER
---------- ----------
         2        132
         2        133
         2        134

현재 3개의 블록을 사용중임

 

 

현재 db의 block size 확인

1
2
3
4
5
SQL> show parameter db_block_size
 
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_block_size                        integer     8192

 

 

해당 데티터 파일 경로 확인

1
2
3
4
5
6
7
8
SQL> 
set lines 200 pages 1000
col file_name for a50
select file_name from dba_data_files where file_id = 2;
 
FILE_NAME
--------------------------------------------------
/oradata1/ORACLE19/corrupt_tbs1.dbf

 

 

메모리에 올라가있는 블록들을 비우기 위해 버퍼 캐시 플러쉬

1
2
3
SQL> alter system flush buffer_cache;
 
System altered.

 

 

dd 명령어로 데이터파일 손상

1
2
3
4
$ dd of=/oradata1/ORACLE19/corrupt_tbs1.dbf bs=8192 seek=132 conv=notrunc count=1 if=/dev/zero
1+0 records in
1+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.000101122 s, 81.0 MB/s

dd 명령어 설명 :
of=/oradata1/ORACLE19/corrupt1.dbf : 데이터를 기록할 출력 파일 경로
bs=8192 : 블록 크기를 8192 바이트(8KB)로 설정
seek=132 : 출력 파일에서 132번째 블록에 데이터를 기록하도록 지정
conv=notrunc : 출력 파일의 크기를 자르지 않고 기존 크기를 유지하도록 지정
count=1 : 한 블록(8192 바이트)만 기록
if=/dev/zero : 입력 파일로 /dev/zero를 사용
요약 : 대상 블록(132)을 0으로 덮어씌우는 작업을 수행

 

 

샘플 테이블 count 확인

1
2
3
4
5
6
7
8
SQL>
conn imsi/imsi
select count(*) from corrupt_test1;
select count(*) from corrupt_test1
       *
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 2, block # 132)
ORA-01110: data file 2: '/oradata1/ORACLE19/corrupt_tbs1.dbf'

에러가 발생함

 

 

alert log 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ tail -300f /ORA19/app/oracle/diag/rdbms/oracle19/oracle19/trace/alert_oracle19.log
2025-01-25T14:35:25.712522+09:00
Hex dump of (file 2, block 132) in trace file /ORA19/app/oracle/diag/rdbms/oracle19/oracle19/trace/oracle19_ora_619870.trc
 
Corrupt block relative dba: 0x00800084 (file 2, block 132)
Completely zero block found during multiblock buffer read
 
Reading datafile '/oradata1/ORACLE19/corrupt_tbs1.dbf' for corrupt data at rdba: 0x00800084 (file 2, block 132)
Reread (file 2, block 132) found same corrupt data (no logical check)
2025-01-25T14:35:25.716575+09:00
Corrupt Block Found
         TIME STAMP (GMT) = 01/25/2025 14:35:25
         CONT = 0, TSN = 7, TSNAME = CORRUPT_TS1
         RFN = 2, BLK = 132, RDBA = 8388740
         OBJN = 73196, OBJD = 73196, OBJECT = CORRUPT_TEST1, SUBOBJECT =
         SEGMENT OWNER = IMSI, SEGMENT TYPE = Table Segment
Errors in file /ORA19/app/oracle/diag/rdbms/oracle19/oracle19/trace/oracle19_ora_619870.trc  (incident=59343):
ORA-01578: ORACLE data block corrupted (file # 2, block # 132)
ORA-01110: data file 2: '/oradata1/ORACLE19/corrupt_tbs1.dbf'
Incident details in: /ORA19/app/oracle/diag/rdbms/oracle19/oracle19/incident/incdir_59343/oracle19_ora_619870_i59343.trc
2025-01-25T14:35:26.369714+09:00
*****************************************************************
An internal routine has requested a dump of selected redo.
This usually happens following a specific internal error, when
analysis of the redo logs will help Oracle Support with the
diagnosis.
It is recommended that you retain all the redo logs generated (by
all the instances) during the past 12 hours, in case additional
redo dumps are required to help with the diagnosis.
*****************************************************************
2025-01-25T14:35:28.353012+09:00
 
Corrupt block relative dba: 0x00800084 (file 2, block 132)
 
Completely zero block found during validation
Reread of blocknum=132, file=/oradata1/ORACLE19/corrupt_tbs1.dbf. found same corrupt data
Reread of blocknum=132, file=/oradata1/ORACLE19/corrupt_tbs1.dbf. found same corrupt data
Reread of blocknum=132, file=/oradata1/ORACLE19/corrupt_tbs1.dbf. found same corrupt data
Reread of blocknum=132, file=/oradata1/ORACLE19/corrupt_tbs1.dbf. found same corrupt data
Reread of blocknum=132, file=/oradata1/ORACLE19/corrupt_tbs1.dbf. found same corrupt data
Checker run found 1 new persistent data failures

 

 

해당 테이블 검증

1
2
3
4
5
6
SQL> analyze table corrupt_test1 validate structure online;
analyze table corrupt_test1 validate structure online
*
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 2, block # 132)
ORA-01110: data file 2: '/oradata1/ORACLE19/corrupt_tbs1.dbf'

블록 커럽트가 있다고 나옴

 

 

참고로 정상 테이블의 경우 validate structure 수행시 "Table analyzed." 라고만 표시됨

1
2
3
SQL> analyze table emp validate structure online;
 
Table analyzed.

 

 

*참고
만약 alert log를 먼저 확인했고 어떤 테이블이 corrupt 되었는지 모르는 경우 아래 쿼리로 file_id와 block로 해당 테이블 확인가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SQL> 
set lines 200 pages 1000
col tablespace_name for a15
col owner for a10
col segment_name for a20
col segment_type for a20
select tablespace_name, owner, segment_name, segment_type
from dba_extents
where file_id = 2
and 132 between block_id and block_id + blocks - 1;
 
TABLESPACE_NAME OWNER      SEGMENT_NAME         SEGMENT_TYPE
--------------- ---------- -------------------- --------------------
CORRUPT_TS1     IMSI       CORRUPT_TEST1        TABLE

 

 

rman 에서 blockrecover 시도

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ rman target /
RMAN> blockrecover datafile 2 block 132;
 
Starting recover at 2025-01-25 14:36:20
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=18 device type=DISK
 
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of recover command at 01/25/2025 14:36:21
RMAN-06026: some targets not found - aborting restore
RMAN-06023: no backup or copy of datafile 2 found to restore

백업이 없어서 복구 안됨

 

 

DBMS_REPAIR 패키지를 이용하여 손상 블록 skip 진행
REPAIR_TABLE 을 담을 테이블 스페이스 지정 및 REPAIR_TABLE 생성

1
2
3
4
5
6
7
8
9
10
11
12
SQL> 
conn / as sysdba
BEGIN
  DBMS_REPAIR.ADMIN_TABLES (
  TABLE_NAME => 'REPAIR_TABLE',
  TABLE_TYPE => dbms_repair.repair_table,
  ACTION => dbms_repair.create_action,
  TABLESPACE => 'USERS');
END;
/
 
PL/SQL procedure successfully completed.

 

 

ORPHAN_KEYS_TABLE 생성(인덱스와 관련된 프로시저)

1
2
3
4
5
6
7
8
9
10
11
SQL> 
BEGIN
  DBMS_REPAIR.ADMIN_TABLES (
  TABLE_NAME => 'ORPHAN_KEYS_TABLE', 
  TABLE_TYPE => DBMS_REPAIR.ORPHAN_TABLE, 
  ACTION => DBMS_REPAIR.CREATE_ACTION,
  TABLESPACE => 'USERS');
END;
/
 
PL/SQL procedure successfully completed.

 

 

CORRUPT_TEST 테이블 CHECK_OBJECT 로 손상 블록 식별

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SQL> 
set serveroutput on
DECLARE num_corrupt INT;
BEGIN
  num_corrupt := 0;
  DBMS_REPAIR.CHECK_OBJECT (
  SCHEMA_NAME => 'IMSI',
  OBJECT_NAME => 'CORRUPT_TEST1',
  REPAIR_TABLE_NAME => 'REPAIR_TABLE',
  corrupt_count => num_corrupt);
  DBMS_OUTPUT.PUT_LINE('number corrupt: ' || TO_CHAR (num_corrupt));
END;
/
 
number corrupt: 1
 
PL/SQL procedure successfully completed.

number corrupt가 1로 표시됨

 

 

repiar_table에 식별된 블록 및 손상 타입이 저장됨

1
2
3
4
5
6
7
8
9
10
11
12
SQL> 
set lines 200 pages 1000
col block_id for 99999
col corrupt_type for 99999
col corrupt_description for a20
col repair_description format a40
select relative_file_id, block_id, corrupt_type, corrupt_description, repair_description, marked_corrupt 
from repair_table;
 
RELATIVE_FILE_ID BLOCK_ID CORRUPT_TYPE CORRUPT_DESCRIPTION  REPAIR_DESCRIPTION                       MARKED_COR
---------------- -------- ------------ -------------------- ---------------------------------------- ----------
               2      132         6148                      mark block software corrupt              TRUE

 

 

식별 된 블록을 손상된 것으로 표시(FIX_CORRUPT_BLOCKS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SQL>
set serveroutput on
DECLARE num_fix INT;
BEGIN
  DBMS_REPAIR.FIX_CORRUPT_BLOCKS (
  SCHEMA_NAME => 'IMSI',
  OBJECT_NAME=> 'CORRUPT_TEST1',
  OBJECT_TYPE => dbms_repair.table_object,
  REPAIR_TABLE_NAME => 'REPAIR_TABLE',
  FIX_COUNT=> num_fix);
  DBMS_OUTPUT.PUT_LINE('num fix: ' || to_char(num_fix));
END;
/
 
num fix: 0
 
PL/SQL procedure successfully completed.

이미 손상된걸로 표시된 상태인지 num fix가 0으로 나옴

 

 

식별된 블록 skip 하도록 허용

1
2
3
4
5
6
7
8
9
10
11
SQL>
BEGIN
  DBMS_REPAIR.SKIP_CORRUPT_BLOCKS (
  SCHEMA_NAME => 'IMSI',
  OBJECT_NAME => 'CORRUPT_TEST1',
  OBJECT_TYPE => dbms_repair.table_object,
  FLAGS => dbms_repair.SKIP_FLAG);
END;
/
 
PL/SQL procedure successfully completed.

 

 

샘플 테이블 count 재확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SQL>
conn imsi/imsi
select count(*) from corrupt_test1;
 
  COUNT(*)
----------
       632
 
SQL> select * from corrupt_test1;
 
      COL1 COL2
---------- ----------
       369 HFQFXbJpxf
       370 oAcYbXpqDe
..
       999 JkiowdbdQt
      1000 jncDGSXpnm
 
632 rows selected.

테이블이 에러없이 읽어지지만 1000개중에 632개만 정상적으로 읽어짐

 

 

인덱스를 이용해 count 시도

1
2
3
4
5
SQL> select /*+ gather_plan_statistics index(c corrupt_test1_ix1) */ count(*) from corrupt_test1 c where col1 > 0;
 
  COUNT(*)
----------
      1000

?? 현재 data block 만 skip 해서 그런지 인덱스로 읽으면 1000건으로 표시됨!

 

 

xplan 확인

1
2
3
4
5
6
7
8
SQL> select * from dbms_xplan.display_cursor(null, null, 'advanced allstats last');
---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation         | Name              | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                   |      1 |        |       |     5 (100)|          |      1 |00:00:00.01 |       3 |
|   1 |  SORT AGGREGATE   |                   |      1 |      1 |    13 |            |          |      1 |00:00:00.01 |       3 |
|*  2 |   INDEX RANGE SCAN| CORRUPT_TEST1_IX1 |      1 |    632 |  8216 |     5   (0)| 00:00:01 |   1000 |00:00:00.01 |       3 |
---------------------------------------------------------------------------------------------------------------------------------

인덱스만 읽고 테이블까지 안가지만 인덱스를 잘 이용함(buffers는 3임)

 

 

인덱스를 이용해 corrupt_test1_ix1 인덱스 컬럼만 조회 시도

1
2
3
4
5
6
7
8
9
10
11
SQL> select /*+ gather_plan_statistics index(c corrupt_test1_ix1) */ col1 from corrupt_test1 c where col1 > 0;
 
      COL1
----------
         1
         2
..
       999
      1000
 
1000 rows selected.

1000개 데이터 모두 조회됨(corrupt_test1_ix1 인덱스에만 존재하는 데이터)

 

 

xplan 확인

1
2
3
4
5
6
7
SQL> select * from dbms_xplan.display_cursor(null, null, 'advanced allstats last');
--------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation        | Name              | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |                   |      1 |        |       |     5 (100)|          |   1000 |00:00:00.01 |      19 |
|*  1 |  INDEX RANGE SCAN| CORRUPT_TEST1_IX1 |      1 |    632 |  8216 |     5   (0)| 00:00:01 |   1000 |00:00:00.01 |      19 |
--------------------------------------------------------------------------------------------------------------------------------

인덱스만 읽고 테이블까지 안가지만 인덱스를 잘 이용함(buffers는 19를 읽음)

 

 

인덱스 읽고 테이블 엑세스까지 하게끔 조회

1
2
3
SQL> select /*+ gather_plan_statistics index(c corrupt_test1_ix1) */ * from corrupt_test1 c where col1 > 0;
 
no rows selected

?? 이 경우에는 no rows로 표시됨

 

 

xplan 확인

1
2
3
4
5
6
7
8
SQL> select * from dbms_xplan.display_cursor(null, null, 'advanced allstats last');
---------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name              | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                   |      1 |        |       |     5 (100)|          |      0 |00:00:00.01 |       3 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| CORRUPT_TEST1     |      1 |    632 | 12640 |     5   (0)| 00:00:01 |      0 |00:00:00.01 |       3 |
|*  2 |   INDEX RANGE SCAN                  | CORRUPT_TEST1_IX1 |      1 |    632 |       |     5   (0)| 00:00:01 |      1 |00:00:00.01 |       2 |
---------------------------------------------------------------------------------------------------------------------------------------------------

인덱스만 읽고 테이블까지 안가지만 인덱스를 잘 이용함(buffers는 3임)

 

 

dump_orphan_keys 프로시저로 corrupt data block내의 row를 가리키고 있는 인덱스 엔트리 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SQL> 
conn / as sysdba
var key_count number
BEGIN
  DBMS_REPAIR.DUMP_ORPHAN_KEYS(
  SCHEMA_NAME=>'IMSI',
  OBJECT_NAME=>'CORRUPT_TEST1_IX1',
  OBJECT_TYPE=>DBMS_REPAIR.INDEX_OBJECT,
  REPAIR_TABLE_NAME=>'REPAIR_TABLE',
  ORPHAN_TABLE_NAME=>'ORPHAN_KEYS_TABLE',
  KEY_COUNT=>:KEY_COUNT);
  END;
/
 
PL/SQL procedure successfully completed.

 

 

key_count 확인

1
2
3
4
5
SQL> print key_count
 
KEY_COUNT
----------
       368

368건이 존재함

 

 

대상 인덱스 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SQL> 
set lines 200 pages 1000
col schema_name for a10
col index_name for a20
col table_name for a20
col keyrowid for a20
select schema_name, index_name, index_id, table_name, keyrowid 
from orphan_keys_table;
 
SCHEMA_NAM INDEX_NAME             INDEX_ID TABLE_NAME           KEYROWID
---------- -------------------- ---------- -------------------- --------------------
IMSI       CORRUPT_TEST1_IX1         73197 CORRUPT_TEST1        AAAR3sAIQAAAIAAAEA
IMSI       CORRUPT_TEST1_IX1         73197 CORRUPT_TEST1        AAAR3sAIQAAAIAAAIA
..
IMSI       CORRUPT_TEST1_IX1         73197 CORRUPT_TEST1        AAAR3sAIQAAAIAAAQB
IMSI       CORRUPT_TEST1_IX1         73197 CORRUPT_TEST1        AAAR3sAIQAAAIAABQB
 
368 rows selected.

 

 

368개의 orphan key들을 정리하기 위해 인덱스 rebuild 수행

1
2
3
4
5
SQL> 
conn imsi/imsi
alter index corrupt_test1_ix1 rebuild online;
 
Index altered.

 

 

인덱스를 이용해 count 시도

1
2
3
4
5
SQL> select /*+ gather_plan_statistics index(c corrupt_test1_ix1) */ count(*) from corrupt_test1 c where col1 > 0;
 
  COUNT(*)
----------
       632

정상적으로 632건 조회됨

 

 

xplan 확인

1
2
3
4
5
6
7
8
SQL> select * from dbms_xplan.display_cursor(null, null, 'advanced allstats last');
------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation         | Name              | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers | Reads  |
------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                   |      1 |        |       |     7 (100)|          |      1 |00:00:00.01 |       3 |      4 |
|   1 |  SORT AGGREGATE   |                   |      1 |      1 |    13 |            |          |      1 |00:00:00.01 |       3 |      4 |
|*  2 |   INDEX RANGE SCAN| CORRUPT_TEST1_IX1 |      1 |    632 |  8216 |     7   (0)| 00:00:01 |    632 |00:00:00.01 |       3 |      4 |
------------------------------------------------------------------------------------------------------------------------------------------

인덱스만 읽고 테이블까지 안가지만 인덱스를 잘 이용함(buffers는 3임)

 

 

인덱스를 이용해 corrupt_test1_ix1 인덱스 컬럼만 조회 시도

1
2
3
4
5
6
7
8
9
10
11
SQL> select /*+ gather_plan_statistics index(c corrupt_test1_ix1) */ col1 from corrupt_test1 c where col1 > 0;
 
      COL1
----------
       369
       370
..
       999
      1000
 
632 rows selected.

rebuild 이후 632개 데이터만 조회됨(corrupt_test1_ix1 인덱스에만 존재하는 데이터)

 

 

xplan 확인

1
2
3
4
5
6
7
SQL> select * from dbms_xplan.display_cursor(null, null, 'advanced allstats last');
--------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation        | Name              | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |                   |      1 |        |       |     7 (100)|          |    632 |00:00:00.01 |      13 |
|*  1 |  INDEX RANGE SCAN| CORRUPT_TEST1_IX1 |      1 |    632 |  8216 |     7   (0)| 00:00:01 |    632 |00:00:00.01 |      13 |
--------------------------------------------------------------------------------------------------------------------------------

인덱스만 읽고 테이블까지 안가지만 인덱스를 잘 이용함(buffers는 13를 읽음)

리빌드 전에 비해 6이 줄어듬

 

 

인덱스 읽고 테이블 엑세스까지 하게끔 조회

1
2
3
4
5
6
7
8
9
10
11
SQL> select /*+ gather_plan_statistics index(c corrupt_test1_ix1) */ * from corrupt_test1 c where col1 > 0;
 
      COL1 COL2
---------- ----------
       369 HFQFXbJpxf
       370 oAcYbXpqDe
..
       999 JkiowdbdQt
      1000 jncDGSXpnm
 
632 rows selected.

정상적으로 632건 조회됨

 

 

xplan 확인

1
2
3
4
5
6
7
8
SQL> select * from dbms_xplan.display_cursor(null, null, 'advanced allstats last');
---------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name              | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                   |      1 |        |       |     9 (100)|          |    632 |00:00:00.01 |      25 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| CORRUPT_TEST1     |      1 |    632 | 12640 |     9   (0)| 00:00:01 |    632 |00:00:00.01 |      25 |
|*  2 |   INDEX RANGE SCAN                  | CORRUPT_TEST1_IX1 |      1 |    632 |       |     7   (0)| 00:00:01 |    632 |00:00:00.01 |      13 |
---------------------------------------------------------------------------------------------------------------------------------------------------

테이블까지 가게되는 경우 총 buffers를 25읽음

 

 

샘플 테이블의 전체 datafile number, block number 확인

1
2
3
4
5
6
7
8
9
SQL> 
select distinct dbms_rowid.rowid_relative_fno(rowid) fno, dbms_rowid.rowid_block_number(rowid) blk_number
from corrupt_test1
order by 1, 2;
 
       FNO BLK_NUMBER
---------- ----------
         2        133
         2        134

132블록은 이제 표시되지 않음

 

 

테이블 스킵 내역 확인

1
2
3
4
5
6
7
8
9
10
11
12
SQL> 
set lines 200 pages 1000
col owner for a10
col table_name for a20
select owner, table_name, skip_corrupt
from dba_tables
where owner='IMSI'
and table_name='CORRUPT_TEST1';
 
OWNER      TABLE_NAME           SKIP_COR
---------- -------------------- --------
IMSI       CORRUPT_TEST         ENABLED

skip_corrupt에 enabled로 표시됨

 

 

db 풀백업 진행

1
2
$ rman target /
RMAN> backup database;

 

 

참고용. 테스트한 테이블 및 테이블스페이스 모두 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SQL> 
conn imsi/imsi
drop table corrupt_test1 purge;
 
conn / as sysdba
BEGIN
  DBMS_REPAIR.ADMIN_TABLES (
  TABLE_NAME => 'ORPHAN_KEYS_TABLE', 
  TABLE_TYPE => DBMS_REPAIR.ORPHAN_TABLE, 
  ACTION => DBMS_REPAIR.DROP_ACTION);
END;
/
 
BEGIN
  DBMS_REPAIR.ADMIN_TABLES (
  TABLE_NAME => 'REPAIR_TABLE',
  TABLE_TYPE => dbms_repair.repair_table,
  ACTION => dbms_repair.drop_action);
END;
/
 
drop tablespace corrupt_ts1 including contents and datafiles;

 

 

결론 :
블록 커럽트 문제가 발생했을때 테이블 전체를 못읽게 되는데 이때 dbms_repair를 이용해 해당블록을 skip 하는식으로 복구가 가능함
하지만 skip 한 블록은 영원히 읽지 못하기 때문에 완전하게 복구를 하려면 rman으로 주기적으로 백업을 하고
rman의 blockrecover 기능을 이용해서 커럽트가 발생하기 이전으로 돌려놔야함
그리고 dbms_repair로 복구를 했을지라도 인덱스를 리빌드해주지 않으면 결과가 안나오거나 잘못된 결과가 나올수 있으니 꼭 인덱스 리빌드까지 해주어야함
위 작업 이후 db를 풀백업하는것이 좋음

 

 

참조 : 

https://positivemh.tistory.com/655
https://docs.oracle.com/en/database/oracle/oracle-database/19/admin/managing-schema-objects.html#GUID-4D413507-A992-44B0-8366-867B78E0B6FF
https://docs.oracle.com/en/database/oracle/oracle-database/19/admin/repairing-corrupted-data.html#GUID-21282FF3-1E98-478E-8111-0A7CAFCFCD97
https://docs.oracle.com/cd/A91034_01/DOC/server.901/a90117/repair.htm
https://ora600tom.wordpress.com/2010/10/15/oracle-block-corruption-and-ora-08103/
https://www.linux.co.kr/bbs/board.php?bo_table=lecture&wr_id=732
https://rastalion.dev/block-corruption-%EB%B0%9C%EC%83%9D%EC%8B%9C/

본 글은 개인적인 경험과 견해를 바탕으로 작성된 것이며,
제가 현재 또는 과거에 재직했던 기업의 공식 입장이나 의견을 대변하지 않습니다.
궁금하신 점은 언제든지 댓글이나 메일로 남겨 주세요. 확인 후 답변드리겠습니다.
감사합니다.