Overblog
Editer l'article Suivre ce blog Administration + Créer mon blog
LAMI DBA

Articles autour des SGBD Oracle, SQL Server & PostgreSQL

ORACLE - Détecter et corriger la Corruption de données

Dans tous environnements, qu'ils soient en production ou non, nous ne sommes jamais à l'abri d'avoir des erreurs de corruption de données, qui peuvent impacter le fonctionnement d'une application compte tenu de datas qui pourraient être incohérentes.

Bien sur dans le cas d'indexes corrompus, le problème peut être rapidement résolu via une reconstruction de ce dernier, mais dans le cas d'une table, c'est tout de suite un peu plus compliqué..

Prennons l'exemple d'une erreur identifiée dans l'alert.log de la base :

Errors in file d:\bin\app\diag\rdbms\MYDB\MYDB\trace\MYDB_ora_4572.trc (incident=2859705):
ORA-01578: bloc de données ORACLE altéré (fichier # 5, bloc # 129165)
ORA-01110: fichier de données 5 : 'D:\WRK\ORACLE\ORADATA\MYDB\DATAFILE\O1_MF_DBMASTER_8GCJZHGT_.DBF'
ORA-26040: Le bloc de données a été chargé à l'aide de l'option NOLOGGING

Ici ce qu'il faut bien voir, en dehors du fichier trc, c'est que nous avons un bloc corrompu pour un fichier oracle, et la base n'est pas en archivelog .. ce qui ne laisse que peu d'option pour tenter de résoudre ce problème.

On lance un premier diagnostic avec l'utilitaire maintenant devenu commun "DBVerify" :

$ORACLE_HOME/bin/dbv file=D:\WRK\ORACLE\ORADATA\MYDB\DATAFILE\O1_MF_DBMASTER_8GCJZHGT_.DBF blocksize=8192 logfile=/tmp/dbv.log

Note : La valeur du blocksize est en fonction du paramétrage de l'instance pour ce tablespace (peut être 16k, 32k ..). Je préconise également de générer une log pour étudier le contenu en détail.

Résultat de l'éxécution de dbverify :

DBVERIFY - Début de vérification : FILE = D:\WRK\ORACLE\ORADATA\MYDB\DATAFILE\O1_MF_DBMASTER_8GCJZHGT_.DBF

DBVERIFY - Vérification terminée

Nbre de pages examinées : 139520
Nbre de pages traitées (Données) : 13144
Nbre de pages en échec (Données) : 0
Nbre de pages traitées (Index) : 6473
Nbre de pages en échec (Index) : 0
Nbre de pages traitées (Autre) : 116390
Nbre de pages traitées (Seg) : 0
Nbre de pages en échec (Seg) : 0
Nbre de pages vides : 3513
Nbre de pages marquées altérées : 169
Nbre de pages Influx : 0
Nbre de pages cryptées : 0
SCN de bloc le plus élevé : 352031051 (0.352031051)

On constate bel et bien une jolie corruption de datas ..

Si l'on identifie la table impactée (ou index), la commande ANALYZE TABLE VALIDATE STRUCTURE peut être pratique, pour identifier les lignes impactées, au travers de la vue INVALID_ROWS. On peut toujours, si l'on ne connait pas la table, générer un petit SQL pour analyser les tables du schéma impacté.

Vous allez me dire, oui mais je ne connais pas le schéma concerné ! Ok, alors voici une astuce simple :

SQL> select distinct owner from dba_tables where tablespace_name = (select tablespace_name from dba_data_files where file_name='&1');
OWNER
-----------------
MYSCHEMA

Nous avons notre schéma impacté par cette corruption, lançons donc un petit sql pour analyser les tables :

SQL> set lines 200
SQL> set pages 0
prompt On creer la vue INVALID_ROWS ..
SQL> @?/rdbms/admin/UTLVALID.SQL
Table crÚÚe.
SQL> spool /tmp/analyze.sql
SQL> select 'analyze table MYSCHEMA.'||table_name||' validate structure cascade;' from dba_tables where owner='MYSCHEMA';
SQL> spoo off
SQL> @/tmp/analyze.sql
Table analysÚe.
Table analysÚe.
Table analysÚe.
Table analysÚe.
...
...
SQL> select * from invalid_rows;
aucune ligne selectionee

Ah je n'ai aucune infos .. ma corruption porterait t'elle sur un autre type d'objet ??

Pour le découvrir, j'utilise notre ami RMAN, en lançant une vérification (VALIDATE) logique de notre fichier de données endommagé :

RMAN> backup validate check logical datafile 5;

ou

RMAN> VALIDATE check logical datafile 'D:\WRK\ORACLE\ORADATA\MYDB\DATAFILE\O1_MF_DBMASTER_8GCJZHGT_.DBF';

ou

RMAN> VALIDATE CHECK LOGICAL TABLESPACE MYTBS;

Cette instruction va lancer un "faux" backup. Autrement dit, il n'y aura pas de fichiers de sauvegarde de générés. Ce faux backup va nous permettre de découvrir les objets liés au bloc(s) oracle endommagés.

Demarrage de backup dans 24/09/15
utilisation du fichier de contr¶le de la base de donnÚes cible au lieu du catal
gue de rÚcupÚration
canal affectÚ : ORA_DISK_1
canal ORA_DISK_1 : SID=30 type d'unitÚ=DISK
canal ORA_DISK_1 : dÚmarrage de l'ensemble de sauvegarde de tous les fichiers d
donnÚes
canal ORA_DISK_1 : insertion du(des) fichier(s) de donnÚes dans l'ensemble de s
uvegarde
fichier de donnÚes en entrÚe, numÚro=00005, nom=D:\WRK\ORACLE\ORADATA\MYDB\DAT
FILE\O1_MF_DBMASTER_8GCJZHGT_.DBF
canal ORA_DISK_1 : ensemble de sauvegarde terminÚ, temps ÚcoulÚ : 00:00:03
Liste des fichiers de donnÚes
=================
File Status Marked Corrupt Empty Blocks Blocks Examined High SCN
---- ------ -------------- ------------ --------------- ----------
5 OK 169 3513 139520 352035662
Nom de fichier : D:\WRK\ORACLE\ORADATA\MYDB\DATAFILE\O1_MF_DBMASTER_8GCJZHGT
.DBF
Block Type Blocks Failing Blocks Processed
---------- -------------- ----------------
Data 0 13144
Index 0 6473
Other 0 116390

Fin de backup dans 24/09/15

plus globalement, si nous voulons scanner toute la base, la commande ci dessous peut être utilisée :

RMAN> BACKUP VALIDATE CHECK LOGICAL DATABASE ARCHIVELOG ALL ;

ou, dans le cas où nous aurions une DB en NOARCHIVELOG (ce qui est mon cas)

RMAN> BACKUP VALIDATE CHECK LOGICAL DATABASE;

Reprenons la log RMAN générée après notre exécution de VALIDATE CHECK LOGICAL. On s'aperçoit qu'aucun bloc n'est marqué FAILING. Qu'importe, il faut aller plus loin :

La requète SQL ci dessous permet d'avoir les informations pour identifier qui / quoi est corrompu :

SQL>COLUMN owner FORMAT A20
SQL>COLUMN segment_name FORMAT A30

SQL>SELECT DISTINCT owner, segment_name
FROM v$database_block_corruption dbc
JOIN dba_extents e ON dbc.file# = e.file_id 
AND dbc.block# BETWEEN e.block_id and e.block_id+e.blocks-1
ORDER BY 1,2;

OWNER SEGMENT_NAME
-------------------- ---------------------------
MYSCHEMA SYS_LOB0000062083C00008$$

Nous avons identifié le segment impacté, un BLOB.

Pour déterminer la table et colonne concernée par ce segment :

SQL> select table_name,column_name from dba_lobs where segment_name='SYS_LOB0000062083C00008$$' and owner='MYSCHEMA';

TABLE_NAME COLUMN_NAME
-------------- ---------------
MYTABLE VALUE

=> Nous avons maintenant isolé précisémment l'impact de la corruption de données.

Pour la résoudre, plusieurs solutions s'offrent à nous.

La méthode universelle, qui fonctionne avec une base en ARCHIVELOG ET NOARCHIVELOG, est de passer par un import de données (à partir d'un dump datant d'avant la corruption bien sur), en ayant au préalable supprimé et créer le Tablespace / fichier de données.

Pour le cas des bases en Archivelog, RMAN peut être utilisé via la notion de blockrecover.

Exemple

RMAN>connect target /
RMAN>BLOCKRECOVER DATAFILE 5 BLOCK 129165;
RMAN>BLOCKRECOVER CORRUPTION LIST RESTORE UNTIL TIME 'SYSDATE - 7';

Ces commandes permettent de récupérer via les sauvegardes RMAN, ainsi que via les archivelogs, le contenu du block endommagé. L'instruction BLOCKRECOVER CORRUPTION LIST RESTORE  permettant quant à elle de restaurer tous les blocs endommagés, qui sont référencés dans la "corruption list" (alimentée après un validate RMAN), le tout en utilisant un UNTIL TIME pour éviter de retrouver la corruption !

DBMS_REPAIR est également une alternative efficace, qui permet d'isoler les données endommagées par la corruption et de fixer les blocs corrompus de telle façon à ce qu'Oracle évite d'avoir à les réutiliser.
Il permet donc ET de détecter la corruption de données, ET de la réparer en "mettant de coté" les blocs endommagés.

Voici un exemple d'utilisation de ce package, qui nescessite l'utilisation de 2 tables spécifiques, l'une pour stocker la liste des blocs corrompus, et la seconde qui stocke les indexes liés à ces blocs (cas de blocs concernant une ou plusieurs tables)

SQL> BEGIN
DBMS_REPAIR.admin_tables (
table_name => 'REPAIR_TABLE',
table_type => DBMS_REPAIR.repair_table,
action => DBMS_REPAIR.create_action,
tablespace => 'SYSAUX');

DBMS_REPAIR.admin_tables (
table_name => 'ORPHAN_KEY_TABLE',
table_type => DBMS_REPAIR.orphan_table,
action => DBMS_REPAIR.create_action,
tablespace => 'SYSAUX');
END;
/

Nous pouvons maintenant vérifier la table impactée via la procédure CHECK_OBJECT :

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
v_num_corrupt INT;
BEGIN
v_num_corrupt := 0;
DBMS_REPAIR.check_object (
schema_name => 'MYSCHEMA',
object_name => 'MYTABLE',
repair_table_name => 'REPAIR_TABLE',
corrupt_count => v_num_corrupt);
DBMS_OUTPUT.put_line('number corrupt: ' || TO_CHAR (v_num_corrupt));
END;
/

number corrupt: 1

-> Ici nous voyons un bloc corrompu de détecté. Nous pouvons donc interroger le contenu des colonnes CORRUPTION_DESCRIPTION et REPAIR_DESCRIPTION de la table REPAIR_TABLE pour en savoir plus sur la corruption identifiée.

Maintenant, nous avons vérifié la corruption, mais les blocs endommagés n'ont pas été fixés. La procédure FIX_CORRUPT_BLOCKS permet de fixer ces blocs, dans l'objectif que ces derniers ne soient pas utilisés par Oracle une fois que la corruption aura été résolue.

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
v_num_fix INT;
BEGIN
v_num_fix := 0;
DBMS_REPAIR.fix_corrupt_blocks (
schema_name => 'MYSCHEMA',
object_name => 'MYTABLE',
object_type => Dbms_Repair.table_object,
repair_table_name => 'REPAIR_TABLE',
fix_count => v_num_fix);
DBMS_OUTPUT.put_line('num fix: ' || TO_CHAR(v_num_fix));
END;
/

num fix: 1

-> Le bloc est "fixé" et ne sera plus utilisé par oracle. Vérifions maintenant l'état des indexes qui pointeraient sur la table impactée, l'objectif étant d'identifier si des indexes doivent être reconstruits ou non :

SQL> declare
v_num_orphans INT;
begin
for myidx in ( select index_name from dba_indexes where owner='MYSCHEMA' and table_name='MYTABLE') LOOP
DBMS_REPAIR.dump_orphan_keys (
schema_name => 'MYSCHEMA',
object_name => myidx.index_name,
object_type => DBMS_REPAIR.index_object,
repair_table_name => 'REPAIR_TABLE',
orphan_table_name => 'ORPHAN_KEY_TABLE',
key_count => v_num_orphans);
DBMS_OUTPUT.put_line('orphan key count: ' || TO_CHAR(v_num_orphans));
end loop;
END;
/
orphan key count: 0

==> Dans ce cas d'exemple, je n'ai pas d'indexes qui sont à rebuilder, mais dans le cas où nous aurions des Orphan Key de détectés, un rebuild des indexes concernés est à réaliser.

Pour revenir au sujet des blocs qui ont été marqués / fixés comme corrompus via la procédure FIX_CORRUPT_BLOCKS, ces derniers sont automatiquement supprimés de la FREELIST, il convient alors de la reconstruire :

SQL> BEGIN
DBMS_REPAIR.rebuild_freelists (
schema_name => 'MYSCHEMA',
object_name => 'MYTABLE',
object_type => DBMS_REPAIR.table_object);
END;
/

Enfin, pour être certain que toutes instructions DML ne touchent pas aux blocs corrompus, la procédure SKIP_CORRUPT_BLOCKS doit être lancée :

SQL> BEGIN
DBMS_REPAIR.skip_corrupt_blocks (
schema_name => 'MYSCHEMA',
object_name => 'MYTABLE',
object_type => DBMS_REPAIR.table_object,
flags => DBMS_REPAIR.skip_flag);
END;
/

Et voila, nous avons vu comment détecter et réparer des erreurs de corruption de données, le tout à chaud sans arrêt de service.

Attention néanmoins car certaines actions peuvent impacter le fonctionnement des applications, je pense notamment à la partie "blockrecover" et à la partie "rebuild indexes", il conviendra donc d'être prudent sur ce type d'opérations ;-)

Micka

Partager cet article
Repost0
Pour être informé des derniers articles, inscrivez vous :
Commenter cet article