Rechercher sur arkzoyd.com

31 mars 2008

Entrainez-vous à LogMiner !

Vous serez peut-être amené un jour à retrouver l'ensemble des lignes supprimées par un ordre delete "après la bataille" et après que undo_retention ne vous permette de les retrouver avec SELECT ... AS OF TIMESTAMP .... Dans un tel cas, LogMiner est sans doute l'outil que vous utiliserez alors, que penseriez-vous d'un entraînement ?

Etape 1 : un scénario déjà vu !

Commencez par imaginer un scénario que vous avez peut-être déjà vu dans lequel une table existe depuis "toujours" dans le schéma de votre application :
create table scott.demo(id number)
tablespace users;

begin
for i in 1..1000 loop
insert into scott.demo values (i);
end loop;
end;
/

commit;

alter system archive log current;

select thread#, max(sequence#)
from v$archived_log
group by thread#
order by thread#;

THREAD# MAX(SEQUENCE#)
------- --------------
1 86

SQL> select name from v$archived_log
where sequence#=86 and thread#=1;

NAME
---------------------------------------------------------
/fra/BLACK/archivelog/2008_03_30/o1_mf_1_86_3z0h44tt_.arc
Un jour un développeur qui a accès à la production ou si vous voulez rendre le scénario plus crédible, dites un DBA malhonnête, accède à la base de données et supprime quelques lignes...
delete from scott.demo where id < 112;

commit;
Le temps passe et vous ne vous apercevez pas ! En effet, un TRUNCATE ou un DROP sont assez faciles à détecter : l'application devient assez méconnaissable après une de ces opérations. Bien sur, ça ne veux pas dire que c'est facile à récupérer. Et LogMiner ne vous aidera sûrement pas ! mais un DELETE ou un UPDATE de quelques lignes sont beaucoup plus pernicieux :
alter system archive log current;

select thread#, max(sequence#)
from v$archived_log
group by thread#
order by thread#;

THREAD# MAX(SEQUENCE#)
------- --------------
1 87

select name from v$archived_log
where sequence#=87 and thread#=1;

NAME
---------------------------------------------------------
/fra/BLACK/archivelog/2008_03_30/o1_mf_1_87_3z0ht5jv_.arc
Finalement après quelques jours et la plainte de plusieurs utilisateurs, ça arrive sur votre bureau! Quelqu'un a détruit des données. On pense que l'application a été piratée ; c'est plus rassurant que d'imaginer que quelqu'un a fait une bêtise et ne s'est pas dénoncé ! Et c'est peut-être vrai en plus. Toujours est-il que l'audit est inactif sur votre système et que vous avez l'occasion de devenir une sorte de super administrateur. Ou de faire virer quelqu'un, y compris vous même !

Etape 2 : Ne travaillez pas en production !

Je sais ce que vous vous dites et vous avez raison : Je suis un lâche et un peureux. Je n'interviens jamais en production, à moins que le système soit déjà planté et en tout cas, jamais pour faire un DROP, un TRUNCATE ou un ordre LMD. Pas d'exception pour LogMiner ; Connectez-vous simplement à la production le temps de "DUMP"er le dictionnaire dans les redo logs et déconnectez vous. Commencez par vérifier que votre base de données a des informations complémentaires activées dans les redo logs pour utiliser LogMiner et sinon activez-les, même le temps de copier le dictionnaire :
select SUPPLEMENTAL_LOG_DATA_MIN
from v$database;

SUPPLEME
--------
NO

alter database add supplemental log data;

Database altered.

select SUPPLEMENTAL_LOG_DATA_MIN
from v$database;

SUPPLEME
--------
YES

EXECUTE DBMS_LOGMNR_D.BUILD( -
OPTIONS=> DBMS_LOGMNR_D.STORE_IN_REDO_LOGS);

SELECT NAME FROM V$ARCHIVED_LOG WHERE DICTIONARY_BEGIN='YES';

NAME
---------------------------------------------------------
/fra/BLACK/archivelog/2008_03_30/o1_mf_1_89_3z0j8hxc_.arc
/fra/BLACK/archivelog/2008_03_30/o1_mf_1_90_3z0j8mxh_.arc

SELECT NAME FROM V$ARCHIVED_LOG WHERE DICTIONARY_END='YES';

NAME
---------------------------------------------------------
/fra/BLACK/archivelog/2008_03_30/o1_mf_1_90_3z0j8mxh_.arc

alter database drop supplemental log data;
Nota Bene :
  • Toutes les informations utiles pour utiliser LogMiner sont dans la documentation... Utilisez-la : Oracle Database Utilities, 11g Release 1 : 18 Using LogMiner to Analyze Redo Log Files
  • En fait et à moins que votre système ne souffre explicitement de "Log Sync Waits", j'aurais tendance à penser que ça peut être une excellente idée de fonctionner avec les "Supplemental Log Data". En effet vous pourrez alors simplement tracer des opérations impossible autrement (Sur des clusters de table par exemple) ou connaître le programme qui a exécuté une opération meme si l'audit est inactif. Autrement dit, c'est peut-être une manière simple de différencier une opération qui vient de l'application d'une opération qui vient d'une session SQL*Plus ou d'un autre outil de DBA ou de développeur.
  • Vous devrez également repérer les fichiers d'archivelogs qui vous intéressent en vous basant probablement sur une plage de date pendant laquelle vous suspecter que les opérations ont eu lieu. Cela suppose évidemment que les archivelogs sont toujours la. Il sera d'ailleurs peut-être utile d'allonger la période de rétention de ces fichiers le temps de finir votre travail.
Etape 3 : Session LogMiner

Vous voilà prêt ou presque à lancer la session LogMiner sur une autre base de données. Bien sur, il faut que depuis votre second serveur vous puissiez accéder les fichier d'archivelogs du premier ; ASM en RAC, NFS, CIFS ou scp sont vos amis dans une telle situation. Alors si vous pouvez accéder aux fichiers de archivelogs depuis votre second serveur, vous voila prêt ! Juste quelques précisions :
  • Vous allez créer une session LogMiner ; Celle-ci est liée à une session de base de données et vous ne pourrez donc pas vous déconnecter entre les différentes étapes de vos manipulations de LogMiner. Cette façon de procéder permet d'avoir plusieurs sessions logminer concurrentes sur la même base de données ; Elle a des conséquences sur la manière d'utiliser logMiner surtout si vous voulez scripter une telle session.
  • Une fois votre session logMiner prête, vous voudrez recherchez l'ensemble des ordres SQL correspondant à vos critères (e.g. tous les DELETE sur la table DEMO). Pour ce faire, vous allez interroger la fixed view V$LOGMNR_CONTENTS. Contrairement à la plupart des autres fixed view, le contenu de V$LOGMNR_CONTENTS sera construit dynamiquement en scannant les archivelogs de votre session LogMiner. Dans l'exemple qui suit vous ne scannerez que quelques fichiers de logs donc le cout associez est faible. Dans une situation réelle, il est très probable que la plage d'archive que vous observiez soit très large et donc que la requête de cette vue soit très longue (plusieurs heures). Pour éviter de scanner toute la plage d'archivelogs de multiple fois :
    • Enregistrer le résultat de votre SELECT dans une table avec, par exemple une requête du type CREATE TABLE X AS SELECT... FROM V$LOGMNR_CONTENTS WHERE ....
    • Prenez des critères assez larges pour ensuite examiner votre table résultante du premier SELECT avec plus de précision. Selectionnez un maximum de colonnes de V$LOGMNR_CONTENTS.
    • Ne travaillez pas en production
Enfin voila pour utiliser LogMiner, procédez dans les étapes qui suivent :
begin
DBMS_LOGMNR.ADD_LOGFILE(
LOGFILENAME => '/fra/BLACK/archivelog/2008_03_30/o1_mf_1_87_3z0ht5jv_.arc',
OPTIONS => DBMS_LOGMNR.NEW);

DBMS_LOGMNR.ADD_LOGFILE(
LOGFILENAME => '/fra/BLACK/archivelog/2008_03_30/o1_mf_1_88_3z0j7z3r_.arc',
OPTIONS => DBMS_LOGMNR.ADDFILE);

DBMS_LOGMNR.ADD_LOGFILE(
LOGFILENAME => '/fra/BLACK/archivelog/2008_03_30/o1_mf_1_89_3z0j8hxc_.arc',
OPTIONS => DBMS_LOGMNR.ADDFILE);

DBMS_LOGMNR.ADD_LOGFILE(
LOGFILENAME => '/fra/BLACK/archivelog/2008_03_30/o1_mf_1_90_3z0j8mxh_.arc',
OPTIONS => DBMS_LOGMNR.ADDFILE);

end;
/
  • Démarrez ensuite la session LogMiner avec DBMS_LOGMNR.ADD_LOGFILE. Vous pouvez spécifiez une plage de date, de SCN ou les options de visualisation des données.
EXECUTE DBMS_LOGMNR.START_LOGMNR(OPTIONS => -
DBMS_LOGMNR.DICT_FROM_REDO_LOGS + -
DBMS_LOGMNR.COMMITTED_DATA_ONLY);
  • Effectuez vos requêtes LogMiner et descendez dans le détail de
create table scott.X
as select * from V$LOGMNR_CONTENTS
where operation='DELETE' and table_name='DEMO';

select count(*) from scott.X;

COUNT(*)
----------
111
Voila, c'est terminé ; gardez à l'esprit que LogMiner ne vous donne pas l'ordre SQL initial mais bien un ordre par ligne modifiée. D'autre part, si l'objet n'existe plus lors de la capture du dictionnaire en production, vous ne verrez que l'object id dans votre requête et cela même si vous utilisez l'option DDL_DICT_TRACKING. En effet cette option permet de traquer les modifications du dictionnaire de données qu'après sa création dans les fichiers de redo logs.

26 mars 2008

Oracle Clusterware vient (cadeau ?) avec Unbreakable Linux

J'avoue qu'il m'a fallu relire 2 fois la Press Release pour comprendre de quoi il s'agissait...

Alors voilà ! Vous pouvez, pour vos serveurs Linux x86 ou x86-64, installer Oracle Enterprise Linux ou Redhat Enterprise Linux ET souscrire au support Oracle (et non pas Redhat !) pour l'un de ces 2 produits. Si c'est votre cas, vous souscrivez à ce qu'Oracle appelle Unbreakable Linux ! Cette offre vous donne accès au référentiel de mise à jour d'Oracle appelé ULN (Unbreakable Linux Network).
Qui cherchait la preuve que les équipes Marketing, même en 2008 peuvent créer des offres incompréhensibles ?
Si c'est votre cas, alors, vous pouvez disposer d'Oracle Clusterware ! Remarquez qu'il n'y rien de nouveau puisque ça fait parti des 4 cas pour lesquels vous pouvez utiliser le clusterware selon le Licencing Guide d'Oracle 11g disponible depuis 8 mois (déjà !). Enfin, c'est sans doute utile de le préciser... J'avoue que j'aime bien le clusterware !

Supprimer un histogramme sur une colonne ou modifier des statistiques manuellement

Parmi les nouvelles fonctionnalités d'Oracle 11g, il est désormais possible de supprimer un histogramme sur une colonne grâce à l'attribut COL_STAT_TYPE de la procédure DBMS_STATS.DELETE_COLUMN_STATS. Vous pouvez vous reporter à la note 5579764.8 pour plus d'informations. En substance, supposons que vous vouliez supprimer un histogramme sur la colonne COL1 de la table TAB1 de l'utilisateur courant, il vous suffit de lancer la commande ci-dessous :
exec dbms_stats.delete_column_stats(ownname=>user, -
tabname => 'TAB1', -
colname=>'COL1',-
col_stat_type=>'HISTOGRAM')
Jusqu'en 10g, la méthode supportée pour "arriver" au même résultat est de recalculer les statistiques en changeant l'attribut method_opt. En plus d'être peut-être un calcul coûteux pour votre base de données, cette méthode changera très probablement, non seulement les statistiques pour la colonne incriminée mais, si les données ont changé depuis votre dernière collection, changera également d'autres de vos statistiques.

Il existe donc une alternative, quoique la méthode soit non supportée ! Exportez les statistiques, bidouillez-les et réimportez-les. Voici un exemple d'une telle manipulation... Biensur, vous ne pourrez ne vous en prendre qu'à vous seul si c'est une très mauvaise pratique !

Etape 1 : table d'exemple

Vous construirez sans doute une méthode infaillible. Pour ce qui suit, nous nous contenterons d'illustrer la technique avec un exemple simple : 1 table avec 1 colonne et 1 histogramme ; Voici le script pour construire un tel exemple :
create table tab1(col1 number)
tablespace users;

begin
for i in 1..10000 loop
insert into tab1(col1) values (i);
end loop
commit;
end;
/

exec dbms_stats.gather_table_stats(ownname=>user, -
tabname => 'TAB1', -
method_opt =>'FOR COLUMNS COL1 SIZE 254')

select count(*)
from user_histograms
where table_name='TAB1'
and COLUMN_NAME='COL1';

COUNT(*)
----------
255
Etape 2 : Exportez les statistiques de la colonne

Pour ce faire, il suffit de créer une table qui contiendra les statistiques.
exec dbms_stats.create_stat_table(ownname=>user, -
stattab=>'EXPORT_STAT1', -
tblspace=>'USERS');
Puis d'exporter les statistiques de la colonne qui vous intéresse dans la table créée précédemment :
exec dbms_stats.export_column_stats(ownname=>user, -
tabname => 'TAB1', -
colname => 'COL1', -
stattab => 'EXPORT_STAT1',-
statid => 'EXPORT#1');
Vous pouvez vérifier que l'export des statistiques contient bien l'histogramme sur les colonnes comme ci-dessous :
select count(*)
from EXPORT_STAT1
where statid='EXPORT#1'
and C1='TAB1'
and C4='COL1'
and C5=user;

COUNT(*)
--------
255
Etape 3 : Bidouillez les statistiques

Evidemment, ceci est l'étape non supportée et c'est assez délicat de savoir ce qu'il y a exactement dans EXPORT_STAT1, quoique ici on est assez chanceux puisque les statistiques sur la colonne sont répétées pour chacune des valeurs de l'histogramme et seul N9, N10 et N11 changent. Je vous laisse valider que, dans cet exemple, les ordres suivants bidouillent "correctement" nos statistiques :
delete from EXPORT_STAT1
where statid='EXPORT#1'
and C1='TAB1'
and C4='COL1'
and C5=user
and N10>0;

update EXPORT_STAT1
set N9=null, N10=null, N11=null
where N10=0;

commit;
Vous pouvez toujours comparer le résultat obtenu avec des statistiques prises sans histogramme ou pourquoi pas, importer les statistiques en 11g, supprimer les histogrammes et les réexporter en 10g. Enfin, supposons que vous êtes satisfait de vos statistiques...

Etape 4 : ré-importez les statistiques

Le reste n'est donc plus qu'une formalité ; Il faut réimporter les statistiques de la colonne :
exec dbms_stats.import_column_stats(ownname=>user, -
tabname => 'TAB1', -
colname => 'COL1', -
stattab => 'EXPORT_STAT1',-
statid => 'EXPORT#1');
Et vous pouvez vérifier que votre histogramme a disparu
select endpoint_number, endpoint_value
from user_histograms
where table_name='TAB1'
and COLUMN_NAME='COL1'
order by endpoint_number;

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
0 1
1 10000
Etape supplémentaire : Avant d'en finir avec cet exemple

Une autre fonctionnalité intéressante d'Oracle 10g est la possibilité de comparer des statistiques avec la fonction de table diff_table_stats_in_stattab. Voici donc un exemple simple d'utilisation. Commençons par changer les données dans la table et les réexporter dans la même table mais avec un STATID différent (ici 'EXPORT#2') :
begin
for i in 10000..50000 loop
insert into tab1(col1) values (i);
end loop
commit;
end;
/

exec dbms_stats.gather_table_stats(ownname=>user, -
tabname => 'TAB1', -
method_opt =>'FOR COLUMNS COL1 SIZE 254')

exec dbms_stats.export_column_stats(ownname=>user, -
tabname => 'TAB1', -
colname => 'COL1', -
stattab => 'EXPORT_STAT1',-
statid => 'EXPORT#2')
Une fois les exportées, vous pouvez afficher un rapport des différences entre les statistiques grâce à la requête ci-dessous :
set long 10000
set longchunksize 10000
set lines 100
set pages 1000
select report from table(
dbms_stats.diff_table_stats_in_stattab(
user,
'TAB1',
'EXPORT_STAT1',
'EXPORT_STAT1',
10,
'EXPORT#1',
'EXPORT#2'));
Le rapport ressemble à ceci :
REPORT
-------------------------------------------------------------------------------###############################################################################

STATISTICS DIFFERENCE REPORT FOR:
.................................

TABLE : TAB1
OWNER : SCOTT
SOURCE A : User statistics table EXPORT_STAT1
: Statid : EXPORT#1
: Owner : SCOTT
SOURCE B : User statistics table EXPORT_STAT1
: Statid : EXPORT#2
: Owner : SCOTT
PCTTHRESHOLD : 10
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


NO DIFFERENCE IN TABLE / (SUB)PARTITION STATISTICS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

COLUMN STATISTICS DIFFERENCE:
.............................

COLUMN_NAME SRC NDV DENSITY HIST NULLS LEN MIN MAX SAMPSIZ
...............................................................................

COL1 A 10000 .0001 NO 0 4 C102 C302 10000
B 50000 .000020000 YES 0 5 C102 C306 50001
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


NO DIFFERENCE IN INDEX / (SUB)PARTITION STATISTICS
###############################################################################
Etape supplémentaire : En finir avec cet exemple

Avant de quitter ce post, supprimez les tables et données de cet exemple :
drop table export_stat1 purge;

drop table TAB1 purge;
C'est tout !

15 mars 2008

ORA-00600 : De l'ombre à la lumière en 5 minutes

Vous pouvez être à peu près sur, lorsque vous capturez un message comme celui ci-dessous dans votre fichier alert.log que vous allez être dedans pour quelques temps :
Errors in file /u01/app/oracle/product/10.2.0/db_1/bdump/racdb1_pz99_8329.trc:
ORA-00600: internal error code, arguments: [17112], [0x2A97258110], [], [], [], [], [], []
ORA-10387: parallel query server interrupt (normal)
La bonne nouvelle, c'est que les ORA-00600 sont faciles à diagnostiquer au moins au début et qu'il est assez rare que vous soyez l'heureux "premier" DBA Oracle à la rencontrer. Enfin, si vous voyez quelque chose comme ça, votre premier réflexe sera sans doute de vous précipiter sur l'une des 2 notes suivantes :
  • 260459.1 - How to Analyze Problems Related to Internal Errors (ORA-600) and Core Dumps (ORA-7445) using Metalink
  • 153788.1 - Troubleshoot an ORA-600 or ORA-7445 Error Using the Error Lookup Tool
Le propos de ce post est de mettre en avant le fait que RDA (Remote Diagnostic Agent) inclut un outils nommé ora600 qui peut vous aider à collecter les traces de votre ORA-00600, les formater et vous conduire encore plus vite aux causes probables de votre problème...

Etape 1 : Installer RDA

Pour installer RDA, il suffit de télécharger la distribution associée depuis Metalink
$ su - oracle
$ mkdir rda-4.11
$ cd rda-4.11
$ # Changez la distribution en fonction de la dernière version
$ # de RDA disponible pour votre plate-forme.
$ # Utilisez votre nom d'utilisateur et mode de passe Metalink
$ ftp updates.oracle.com
ftp> cd 6854532
ftp> ls
ftp> get p6854532_4110_Linux-x86.zip
ftp> bye
$ unzip 6854532*.zip
$ cd rda
Etape 2 : Utiliser RDA ora600

Une fois RDA installe, il suffit d'utiliser ora600 en ajoutant le fichier de trace généré par l'erreur ORA-00600 comme ci-dessous :
$ ./rda.pl -T ora600:/u01/app/oracle/product/10.2.0/db_1/bdump/racdb1_pz99_8329.trc
Le résultat s'affiche presque aussitôt :
Diagnosing ORA-600 Oracle internal errors ...

Error Code: 600
- First Argument: 17112
- Other Arguments: 0x2A97258110
Applicable Note:
47411.1 (ORA-600 [17112] Internal Heap Error)

Error Code: 600
- First Argument: 17147
- Other Arguments: 0x2A97258110
Applicable Note:
138580.1 (ORA-600 [17147])

Error Code: 600
- First Argument: 17147
- Other Arguments: 0x2A97258110
Applicable Note:
138580.1 (ORA-600 [17147])

Summary file: /usr/oracle/rda/output/RDA_ORA600_8329_summary.htm
Related Files:
- Trace Header File: /home/oracle/rda/output/RDA_ORA600_8329_header.htm
- Heap Summary File: /home/oracle/rda/output/RDA_ORA600_8329_heap1.htm
- Heap Summary File: /home/oracle/rda/output/RDA_ORA600_8329_heap2.htm
Vous pouvez ainsi directement allez sur les notes correspondantes ou ouvrir les pages web générées pour en savoir plus sur la pile des programmes ou le contenue de la mémoire :
Etape 3 : Résoudre votre problème...

J'ai bien peur que cette dernière étape me dépasse complètement... A moins que vous ne vous décidiez à faire appel à nos services ;-).

Dans l'intervalle et si vous n'avez pas résolu votre problème dans les 5 première minutes, vous devriez ouvrir une SR et vous préparer mentalement à des heures un peu difficiles...

Qui peut sauver votre base de données ?



Regardez la BD du 11 mars !

03 mars 2008

C'est le printemps à Redwood...

Il est 7.12 am à Redwood et en dehors du décalage horaire et de ces fils électriques entre l'hôtel et Oracle Corp, tout se passe très bien, le voyage a été parfait. Voici la vue que j'ai de la fenêtre de ma chambre.


Les cerisiers sont en fleur et je suis impatient d'entrer dans le vif du sujet. PLus que 1 heure 45 à tuer...

01 mars 2008

Un truc excellent à propos de ASM !

Un peu d'auto-satisfaction de nuit à personne, si ? Cette semaine j'ai fait coup double donc puisque :
  • J'ai enfin publié ce post sur lequel j'ai passé beaucoup de temps pour trouver une façon de renommer les DiskGroups ASM. Je dois dire que, merci à la chance !, je suis assez content du résultat (Je regrette juste qu'il n'existe pas de méthode supportée). Remarquez que si vous suivez ce blog et trouvez parfois que je manque d'entrain, peut-être simplement qu'en regardant mes posts en anglais sur le blog de Pythian, vous pourrez trouver d'autres sujets susceptibles de vous intéresser.
  • A partir de dimanche soir (PST), je vous écrirai de Redwood, CA ; La semaine risque d'être plus qu'intéressante... Dommage, il faut que je tienne ma langue à ce propos. Enfin, j'essaierai de poster 2 ou 3 photos des pinguins qui trainent dans les allées.
Allez j'ai encore 94 posts en cours d'écriture alors j'aurais sûrement le temps d'en boucler un dans l'avion de Chicago ou celui de San Francisco.