Recuperação no nível de blocos de dados com o RMAN (Recovery Manager)
Com o RMAN (Recovery Manager) é possível recuperar blocos de dados que foram corrompidos por algum motivo, seja por falhas de hardware, sistema operacional, ou até mesmo por problemas na própria instância do Oracle. Sempre que o erro “ORA-01578: bloco de dados ORACLE danificado (arquivo núm. string, bloco núm. string)” é reportado, significa que o Oracle tentou acessar algum bloco de dados corrompido em um arquivo de dados (datafile). Quando isso acontece, o DBA precisa identificar os blocos que foram corrompidos, a natureza da corrupção, e analisar as opções disponíveis para resolver o problema.
Quais opções temos para resolver o problema e suspender a emissão do erro ORA-01578?
- Se for identificado que o segmento afetado é um índice de tabela, recriá-lo resolverá o problema.
- Caso seja outro segmento como uma tabela, verificar se existe uma cópia da tabela em outro lugar de forma que seja possível reconstruir as linhas afetadas.
- Executar CREATE TABLE … AS SELECT na tabela identificada com blocos corrompidos de forma a isolar as linhas saudáveis das linhas corrompidas.
- Ignorar os blocos corrompidos fazendo uso de algumas procedures da package DBMS_REPAIR. (FIX_CORRUPT_BLOCKS e SKIP_CORRUPT_BLOCKS).
- Realizar uma recuperação do bloco corrompido (Block Media Recovery).
Dentre as opções citadas acima, irei tratar neste artigo a recuperação física do bloco corrompido utilizando a técnica “Block Media Recovery” do RMAN. Para tanto, um backup físico do banco de dados, ou até mesmo um backup físico da tablespace afetada pela corrupção do bloco deverá estar disponível. Vale a pena salientar que o backup a ser utilizado deverá um que foi criado antes do bloco ter sido corrompido. Para realizar uma simulação, irei executar primeiro um backup da tablespace TBS_DATA_01, depois irei corromper alguns blocos no arquivo de dados pertencente a tablespace em questão, e por fim realizar a recuperação dos blocos corrompidos usando o RMAN através do comando BLOCKRECOVER.
[oracle@server01 ~]$ rman target /
Recovery Manager: Release 11.2.0.3.0 - Production on Mon Feb 1 10:27:33 2016
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
connected to target database: BD01 (DBID=3046620119)
RMAN> backup tablespace TBS_DATA_01;
Starting backup at 01/02/2016
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=6 device type=DISK
channel ORA_DISK_1: starting compressed full datafile backup set
channel ORA_DISK_1: specifying datafile(s) in backup set
input datafile file number=00058 name=/data/oracle/BD01/TBS_DATA_01.dbf
channel ORA_DISK_1: starting piece 1 at 01/02/2016
channel ORA_DISK_1: finished piece 1 at 01/02/2016
piece handle=/backup/flash_recovery_area/BD01/backupset/2016_02_01/o1_mf_nnndf_TAG20160105T144408_c8qwps0q_.bkp tag=TAG20160105T144408 comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:00:02
Finished backup at 01/02/2016
Após a realização do backup da tablespace TBS_DATA_01, alvo de blocos de dados que irei corromper mais a frente, segue abaixo a tabela que possivelmente será vítima destes blocos corrompidos. A tabela T1 está alocada na tablespace TBS_DATA_01, no arquivo de dados número 58, e possui atualmente 13 MB de tamanho com cerca de 1 milhão de linhas.
SQL> select segment_name,
2 tablespace_name,
3 header_file,
4 header_block,
5 blocks,
6 bytes
7 from dba_segments
8 where segment_name = 'T1';
SEGMENT_NAME TABLESPACE_NAME HEADER_FILE HEADER_BLOCK BLOCKS BYTES
--------------- ----------------- ----------- ------------ ---------- ----------
T1 TBS_DATA_01 58 130 1664 13631488
1 linha selecionada.
SQL> select count(*) from T1;
COUNT(*)
----------
1000000
1 linha selecionada
Irei executar o comando ANALYZE para validar e certificar que o segmento T1 está íntegro e não possui qualquer problema de corrupção em seus blocos de dados. Se nenhuma mensagem de erro for emitida é porque a tabela está íntegra e consistente.
SQL> analyze table T1 validate structure;
Tabela analisada.
Agora irei corromper alguns blocos do arquivo de dados TBS_DATA_01.dbf. Como o bloco de cabeçalho do datafile é o 130, irei corromper os seguintes blocos acima dele: 140, 240, 340 e 440 conforme demonstrado abaixo.
[oracle@server01 ~]$ dd if=/dev/zero of=TBS_DATA_01.dbf bs=8192 seek=140 conv=notrunc count=1
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 4.9809e-05 s, 164 MB/s
[oracle@server01 ~]$ dd if=/dev/zero of=TBS_DATA_01.dbf bs=8192 seek=240 conv=notrunc count=1
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 4.5978e-05 s, 178 MB/s
[oracle@server01 ~]$ dd if=/dev/zero of=TBS_DATA_01.dbf bs=8192 seek=340 conv=notrunc count=1
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 4.3908e-05 s, 187 MB/s
[oracle@server01 ~]$ dd if=/dev/zero of=TBS_DATA_01.dbf bs=8192 seek=440 conv=notrunc count=1
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 4.3342e-05 s, 189 MB/s
Para garantir que os dados serão lidos diretamente do arquivo de dados TBS_DATA_01.dbf e não do buffer cache na SGA, irei forçar a limpeza do cache e realizar a execução de uma instrução SELECT na tabela T1.
SQL> alter system flush buffer_cache;
Sistema alterado.
Veremos abaixo que tanto a instrução SELECT quanto o comando ANALYZE irão falhar e emitir o erro ORA-01578. Percebe-se que a mensagem de erro virá acompanhada do número do arquivo de dados e do número do bloco afetado. Neste caso, (arquivo núm. 58, bloco núm. 140).
SQL> select count(*) from T1;
select count(*) from T1
*
ERRO na linha 1:
ORA-01578: bloco de dados ORACLE danificado (arquivo núm. 58, bloco núm. 140)
ORA-01110: 58 do arquivo de dados: '/data/oracle/BD01/TBS_DATA_01.dbf'
SQL> analyze table T1 validate structure;
analyze table T1 validate structure
*
ERRO na linha 1:
ORA-01578: bloco de dados ORACLE danificado (arquivo núm. 58, bloco núm. 140)
ORA-01110: 58 do arquivo de dados: '/data/oracle/BD01/TBS_DATA_01.dbf'
Para identificar qual objeto foi afetado pela corrupção do bloco, bastará executar o SQL abaixo que seleciona dados da view de dicionários de dados DBA_EXTENTS. Como demonstrado pelo resultado abaixo, foi comprovado que o segmento afetado pelos blocos corrompidos foi a tabela T1.
SQL> select segment_type,owner,segment_name from dba_extents
2 where file_id = 58 and 131 between block_id
3 and block_id+blocks -1;
SEGMENT_TYPE OWNER SEGMENT_NAME
------------------ ------------------------------ ------------------------------
TABLE SCOTT T1
1 linha selecionada.
Existem outras formas de diagnosticar e coletar maiores informações sobre blocos que foram corrompidos nos arquivos de dados do Oracle. Dentre elas posso citar a utilização do utilitário DBVERIFY e do próprio comando VALIDATE do utilitário RMAN. Para usar o DBVERIFY, irei me certificar do tamanho padrão do bloco utilizado pela tablespace TBS_DATA_01.
SQL> select tablespace_name,block_size
from dba_tablespaces
where tablespace_name = 'TBS_DATA_01';
TABLESPACE_NAME BLOCK_SIZE
------------------------------ ----------
TBS_DATA_01 8192
Abaixo irei fazer uma análise no arquivo de dados TBS_DATA_01.dbf. Para tanto, após entrar no diretório onde está localizado o arquivo de dados, executarei o DBVERIFY através do utilitário dbv.
[oracle@server01 ~]$ dbv blocksize=8192 file=TBS_DATA_01.dbf feedback=1000
DBVERIFY: Release 11.2.0.3.0 - Production on Mon Feb 1 10:49:42 2016
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
DBVERIFY - Verification starting : FILE = /data/oracle/BD01/TBS_DATA_01.dbf
Page 140 is marked corrupt
Corrupt block relative dba: 0x0e80008c (file 58, block 140)
Completely zero block found during dbv:
Page 240 is marked corrupt
Corrupt block relative dba: 0x0e8000f0 (file 58, block 240)
Completely zero block found during dbv:
Page 340 is marked corrupt
Corrupt block relative dba: 0x0e800154 (file 58, block 340)
Completely zero block found during dbv:
Page 440 is marked corrupt
Corrupt block relative dba: 0x0e8001b8 (file 58, block 440)
Completely zero block found during dbv:
DBVERIFY - Verification complete
Total Pages Examined : 1920
Total Pages Processed (Data) : 1520
Total Pages Failing (Data) : 0
Total Pages Processed (Index): 0
Total Pages Failing (Index): 0
Total Pages Processed (Other): 160
Total Pages Processed (Seg) : 0
Total Pages Failing (Seg) : 0
Total Pages Empty : 236
Total Pages Marked Corrupt : 4
Total Pages Influx : 0
Total Pages Encrypted : 0
Highest block SCN : 2253598227 (1825.2253598227)
Acima é possível perceber que os blocos corrompidos foram identificados e prontamente reportados pelo utilitário. Utilizando o comando VALIDATE do RMAN para validar a tablespace TBS_DATA_01, veremos que também será reportado informações sobre blocos corrompidos.
RMAN> validate tablespace TBS_DATA_01;
Starting validate at 01/02/2016
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=6 device type=DISK
channel ORA_DISK_1: starting validation of datafile
channel ORA_DISK_1: specifying datafile(s) for validation
input datafile file number=00058 name=/data/oracle/BD01/TBS_DATA_01.dbf
channel ORA_DISK_1: validation complete, elapsed time: 00:00:01
List of Datafiles
=================
File Status Marked Corrupt Empty Blocks Blocks Examined High SCN
---- ------ -------------- ------------ --------------- ----------
58 FAILED 0 236 1920 7840568913427
File Name: /data/oracle/BD01/TBS_DATA_01.dbf
Block Type Blocks Failing Blocks Processed
---------- -------------- ----------------
Data 0 1520
Index 0 0
Other 4 164
validate found one or more corrupt blocks
See trace file /u01/app/oracle/diag/rdbms/bd01/BD01/trace/BD01_ora_11127.trc for details
Finished validate at 01/02/2016
A partir do Oracle 11g, quando uma instrução SQL é abortada devido a emissão do erro ORA-01578 pelo fato de a mesma ter tentado acessar algum bloco corrompido, o Oracle prontamente já carrega informações na view dinâmica de desempenho V$DATABASE_BLOCK_CORRUPTION com informações do bloco corrompido. No entanto, se eu quiser saber todos os blocos que estão corrompidos, preferencialmente executo o comando VALIDATE DATABASE no RMAN. Desta forma a view será carregada de uma única vez com todos os blocos corrompidos encontrados no banco de dados.
SQL> select * from v$database_block_corruption;
FILE# BLOCK# BLOCKS CORRUPTION_CHANGE# CORRUPTION_TYPE
---------- ---------- ---------- ------------------ --------------------
58 140 1 0 ALL ZERO
58 240 1 0 ALL ZERO
58 340 1 0 ALL ZERO
58 440 1 0 ALL ZERO
4 linhas selecionadas.
No resultado acima, os blocos corrompidos estão listado na coluna BLOCK#. Segue abaixo uma consulta que eu criei que retorna informações adicionais sobre os blocos corrompidos. Essas informações incluem o nome do arquivo de dados, nome da tablespace, nome do objeto que foi afetado, entre outras. Para fins estéticos, eu omiti algumas colunas do resultado do SQL abaixo.
SQL> SELECT file#,
2 file_name,
3 c.tablespace_name,
4 block#,
5 corruption_change#,
6 corruption_type,
7 segment_type,
8 a.owner,
9 segment_name,
10 partition_name,
11 skip_corrupt
12 FROM dba_extents a, V$DATABASE_BLOCK_CORRUPTION b, dba_data_files c, dba_tables d
13 WHERE b.file# = c.file_id
14 AND a.file_id = b.file#
15 AND a.segment_name=d.table_name
16 AND b.block# BETWEEN a.block_id AND a.block_id + a.blocks - 1;
FILE# FILE_NAME TABLESPACE_NAME BLOCK#
------- -------------------------------------- --------------- ---------
58 /data/oracle/BD01/TBS_DATA_01.dbf TBS_DATA_01 140
58 /data/oracle/BD01/TBS_DATA_01.dbf TBS_DATA_01 240
58 /data/oracle/BD01/TBS_DATA_01.dbf TBS_DATA_01 340
58 /data/oracle/BD01/TBS_DATA_01.dbf TBS_DATA_01 440
Agora irei me certificar que realmente possuo um backup da tablespace TBS_DATA_01 conforme demonstrado abaixo.
RMAN> list backup of tablespace TBS_DATA_01;
using target database control file instead of recovery catalog
List of Backup Sets
===================
BS Key Type LV Size Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
4095 Full 1.99M DISK 00:00:01 01/02/2016
BP Key: 4095 Status: AVAILABLE Compressed: YES Tag: TAG20160105T144408
Piece Name: /backup/flash_recovery_area/BD01/backupset/2016_02_01/o1_mf_nnndf_TAG20160105T144408_c8qwps0q_.bkp
List of Datafiles in backup set 4095
File LV Type Ckp SCN Ckp Time Name
---- -- ---- ---------- ---------- ----
58 Full 7840568913504 01/02/2016 /data/oracle/BD01/TBS_DATA_01.dbf
Por fim, irei realizar um recovery do bloco 140 do arquivo de dados 58 fazendo uso do RMAN através do comando BLOCKRECOVER como demonstrado a seguir.
RMAN> blockrecover datafile 58 block 140;
Starting recover at 01/02/2016
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=223 device type=DISK
channel ORA_DISK_1: restoring block(s)
channel ORA_DISK_1: specifying block(s) to restore from backup set
restoring blocks of datafile 00058
channel ORA_DISK_1: reading from backup piece /backup/flash_recovery_area/BD01/backupset/2016_02_01/o1_mf_nnndf_TAG20160105T144408_c8qwps0q_.bkp
channel ORA_DISK_1: piece handle=/backup/flash_recovery_area/BD01/backupset/2016_02_01/o1_mf_nnndf_TAG20160105T144408_c8qwps0q_.bkp tag=TAG20160105T144408
channel ORA_DISK_1: restored block(s) from backup piece 1
channel ORA_DISK_1: block restore complete, elapsed time: 00:00:01
starting media recovery
media recovery complete, elapsed time: 00:00:01
Finished recover at 01/02/2016
Ao acessar a view V$DATABASE_BLOCK_CORRUPTION, poderemos perceber que a linha referente ao bloco 140 desapareceu.
SQL> select * from v$database_block_corruption;
FILE# BLOCK# BLOCKS CORRUPTION_CHANGE# CORRUPTION_TYPE
---------- ---------- ---------- ------------------ --------------------
58 240 1 0 ALL ZERO
58 340 1 0 ALL ZERO
58 440 1 0 ALL ZERO
3 linhas selecionadas.
Se quisermos recuperar todos os blocos listados na view V$DATABASE_BLOCK_CORRUPTION de uma única vez, bastará utilizar o comando abaixo.
RMAN> blockrecover corruption list;
Starting recover at 01/02/2016
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=223 device type=DISK
channel ORA_DISK_1: restoring block(s)
channel ORA_DISK_1: specifying block(s) to restore from backup set
restoring blocks of datafile 00058
channel ORA_DISK_1: reading from backup piece /backup/flash_recovery_area/BD01/backupset/2016_02_01/o1_mf_nnndf_TAG20160105T144408_c8qwps0q_.bkp
channel ORA_DISK_1: piece handle=/backup/flash_recovery_area/BD01/backupset/2016_02_01/o1_mf_nnndf_TAG20160105T144408_c8qwps0q_.bkp tag=TAG20160105T144408
channel ORA_DISK_1: restored block(s) from backup piece 1
channel ORA_DISK_1: block restore complete, elapsed time: 00:00:01
starting media recovery
media recovery complete, elapsed time: 00:00:01
Finished recover at 01/02/2016
Pronto. Os blocos foram recuperados e a instrução SELECT na tabela T1 irá executar sem problemas.
SQL> select * from v$database_block_corruption;
não há linhas selecionadas
SQL> select count(*) from T1;
COUNT(*)
----------
1000000
1 linha selecionada.