Le FLASHBACK QUERY sous Oracle 9i

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Introduction

Et Oracle inventa la machine à remonter le temps

Combien de fois avez-vous du arrêter la base, faire des restaurations UNTIL CANCEL, analyser les redo-logs avec log miner, ou mettre en place des exports de vos tables? Et tout cela pourquoi? Pour un simple UPDATE sans clause WHERE, un DELETE mal ciblé ou mal construit.

Et bien maintenant tout cela est fini. Oracle depuis la version 9i a inventé une nouvelle fonctionnalité aux applications diverses et variées : Le FLASHBACK QUERY.

I. Qu'est-ce que c'est ?

FLASHBACK QUERY est une nouvelle fonctionnalité qui vous permet de voir des données telles quelle étaient dans le temps (en vous basant sur une date ou sur un SCN). A quoi ça sert me direz vous? Et bien fini le temps où il fallait arrêter la base de données juste pour récupérer quelques lignes, maintenant tout cela se fait en ligne. Plus besoin de déranger les utilisateurs, tout se fait de manière transparente pour eux. Très simple d'utilisation (vous entrez une date ou un SCN et hop vous voyez les données telles qu'elles étaient à ce moment la) il peut même ne nécessiter aucune interventions du DBA si l'utilisateur dispose des droits suffisants.

II. Les Avantages

Un avantage majeur de FLASHBACK est qu'il peut être utilisé de manière complètement transparente par des applications qui peuvent ainsi générer des statistiques temporelles sans avoir à sauvegarder quoi que ce soit.

Aucune interruption de service, tous les ordres DML sont autorisés pendant une requête FLASHBACK, même si l'objet en question est utilisé par FLASHBACK. Plusieurs sessions concurrentes peuvent utiliser FLASHBACK en même temps. La durée d'exécution dépendra de la récupération des données nécessaires pour remonter dans le temps.

III. Les limitations

Il faut savoir que pour revenir dans le temps sur une table, il est évident qu'il ne faut pas que la structure de la table ait été modifiée. Si c'est le cas l'erreur ORA-1466 sera automatiquement renvoyée.

Le retour dans le temps basé sur une date n'est précis qu'à plus ou moins 5 minutes, il faudra donc utiliser le SCN qui lui sera beaucoup plus précis.

La durée maximum de sauvegarde est actuellement limitée à 5 jours maximum.

Vous ne pourrez pas imbriquer les appels à FLASHBACK et vous ne pourrez pas utiliser de FLASHBACK Query sur des tables à travers un lien de base de données.

IV. Comment ça marche

IV-A. L'installation

Avant de pouvoir utiliser cet outil, il va vous falloir configurer la base de données.

La première chose à faire va être d'enclencher la gestion automatique des UNDOs à la place de l'ancienne méthode (avec les Rollback Segments)

Pour savoir dans quel mode vous êtes, entrez la commande suivante :

 
Sélectionnez
SELECT name, value
from v$parameter t
where t.NAME in ('undo_management','undo_retention');
Name 			Value
---------------	---------
undo_management	AUTO
undo_retention	10800

Vous devriez voir apparaître AUTO si cette fonctionnalité est enclenchée. Sinon exécutez les commandes suivantes :

 
Sélectionnez
ALTER SYSTEM SET undo_management=AUTO SCOPE=spfile;
ALTER SYSTEM SET undo_retention=10800 SCOPE=both;
CREATE UNDO 
    TABLESPACE "UNDOTBS1" 
    DATAFILE '<PATH>\UNDOTBS01.DBF' SIZE 200M 
    REUSE AUTOEXTEND 
    ON NEXT  5120K MAXSIZE  32767M;

Redémarrez la base de données.

Le paramètre UNDO_MANAGEMENT sert à activer/désactiver le mode UNDO. Les 2 valeurs possibles sont AUTO ou MANUAL

Le paramètre UNDO_RETENTION (en secondes) sert à définir le temps que nous pourrons remonter durant une requête FLASHBACK. Les valeurs possibles vont de 0 à 2^31-1.

Plus vous voudrez revenir loin dans le temps plus le nombre d'informations augmentera et plus vous utiliserez de place.

Ensuite il ne vous restera plus qu'à donner le privilège EXECUTE sur le package DBMS_FLASHBACK à l'utilisateur souhaité.

Vous n'avez pas à choisir la ou les tables que vous souhaitez pouvoir revoir. FlashBack vous permettra de revoir l'ensemble de la base de données telle qu'elle était dans le temps. Donc vous serez en mesure de revoir toutes les tables pour autant que leur structure n'ait pas été changée. La seule chose que vous devez paramétrer est la durée maximale à sauvegarder.

IV-B. La méthode générale

La méthode pour utiliser le FLASHBACK Query se déroule en 3 étapes (que ce soit en SQL ou en PL/SQL) :

  • Retour dans le temps avec (DBMS_FLASHBACK.ENABLE_AT_TIME ou DBMS_FLASHBACK.ENABLE_AT_SYSTEM_CHANGE_NUMBER)
  • Exécution d'une requête qui nous permettra de récupérer les informations souhaitées.
  • Retour dans le présent avec DBMS_FLASHBACK.DISABLE

Attention à partir du moment ou vous êtes retourné dans le passé les seules commandes autorisées sont des commandes SELECT. Il vous faudra donc ramener les données qui vous intéressent dans un curseur.

IV-B-1. Exemples d'utilisation

Pour les exemples d'utilisations que je vous propose, nous utiliserons les bonnes vieilles tables de SCOTT ainsi que le compte SCOTT.

La première étape consiste à donner les droits d'exécution du package DBMS_FLASBACK à SCOTT.

 
Sélectionnez
CONNECT sys/<pass> AS SYSDBA
GRANT EXECUTE ON dbms_flashback TO scott;

Voici les scripts SQL de création et d'alimentation de la table EMP :

 
Sélectionnez
CONNECT scott/tiger@<VOTRE BASE>
CREATE TABLE EMP
       (EMPNO NUMBER(4) NOT NULL,
        ENAME VARCHAR2(10),
        JOB VARCHAR2(9),
        MGR NUMBER(4),
        HIREDATE DATE,
        SAL NUMBER(7, 2),
        COMM NUMBER(7, 2),
        DEPTNO NUMBER(2));

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7369, 'SMITH', 'CLERK', 7902, to_date('17-12-1980', 'dd-mm-yyyy'), 800, NULL, 20);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7499, 'ALLEN', 'SALESMAN', 7698, to_date('20-02-1981', 'dd-mm-yyyy'), 1600, 300, 30);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7521, 'WARD', 'SALESMAN', 7698, to_date('22-02-1981', 'dd-mm-yyyy'), 1250, 500, 30);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7566, 'JONES', 'MANAGER', 7839, to_date('02-04-1981', 'dd-mm-yyyy'), 2975, NULL, 20);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7654, 'MARTIN', 'SALESMAN', 7698, to_date('28-09-1981', 'dd-mm-yyyy'), 1250, 1400, 30);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7698, 'BLAKE', 'MANAGER', 7839, to_date('01-05-1981', 'dd-mm-yyyy'), 2850, NULL, 30);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7782, 'CLARK', 'MANAGER', 7839, to_date('09-06-1981', 'dd-mm-yyyy'), 2450, NULL, 10);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7788, 'SCOTT', 'ANALYST', 7566, to_date('19-04-1987', 'dd-mm-yyyy'), 3000, NULL, 20);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7839, 'KING', 'PRESIDENT', NULL, to_date('17-11-1981', 'dd-mm-yyyy'), 5000, NULL, 10);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7844, 'TURNER', 'SALESMAN', 7698, to_date('08-09-1981', 'dd-mm-yyyy'), 1500, 0, 30);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7876, 'ADAMS', 'CLERK', 7788, to_date('23-05-1987', 'dd-mm-yyyy'), 1100, NULL, 20);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7900, 'JAMES', 'CLERK', 7698, to_date('03-12-1981', 'dd-mm-yyyy'), 950, NULL, 30);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7902, 'FORD', 'ANALYST', 7566, to_date('03-12-1981', 'dd-mm-yyyy'), 3000, NULL, 20);

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (7934, 'MILLER', 'CLERK', 7782, to_date('23-01-1982', 'dd-mm-yyyy'), 1300, NULL, 10);

COMMIT;

IV-C. Récupérer des données dans le temps en utilisant une date

Pour cet exemple nous allons modifier une ligne avec la commande suivante

 
Sélectionnez
UPDATE emp SET sal=6000 WHERE ename='KING';
COMMIT;

Puis nous allons voir si nous arrivons à voir les données telles qu'elles étaient dans le temps (attention si vous ne disposiez pas de la table avant de lancer le flashback query ) il vous faudra attendre 5 minutes)

 
Sélectionnez
EXECUTE DBMS_FLASHBACK.ENABLE_AT_TIME(sysdate- (3/1440));
SELECT * FROM emp where ename='KING';
EXECUTE DBMS_FLASHBACK.DISABLE;

Voici le résultat :

 
Sélectionnez
SQL> EXECUTE DBMS_FLASHBACK.ENABLE_AT_TIME(sysdate- (3/1440));
PL/SQL procedure successfully completed

SQL> select *
  2  from emp
  3  where ename ='KING';

EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7839 KING       PRESIDENT       17/11/1981    5000,00               10

SQL> EXECUTE DBMS_FLASHBACK.DISABLE;
PL/SQL procedure successfully completed

Voila se qui se passerait si vous tentiez de lancer une FLASHBACK QUERY sur une nouvelle table :

 
Sélectionnez
SQL> CREATE TABLE t_test (id number, sal number);

Table created

SQL> INSERT INTO t_test values (1,1000);

1 row inserted

SQL> COMMIT;

Commit complete

SQL> EXECUTE DBMS_FLASHBACK.ENABLE_AT_TIME(sysdate- (3/1440));
PL/SQL procedure successfully completed

SQL> SELECT *
  2  FROM t_test;

SELECT *
FROM t_test
ORA-01466: lecture des données impossible - Définition de tables modifiée

SQL> EXECUTE DBMS_FLASHBACK.DISABLE;
PL/SQL procedure successfully completed

Cette erreur est du au fait que la table n'existait pas avant et qu'aucunes informations n'avaient été enregistrées dans le dictionnaire de données concernant cette table.

Le seul moyen de ramener ces informations dans le présent sera d'utiliser un bloc PL/SQL Anonyme.

IV-D. Ramener des données du passé au présent en utilisant une Date

Voici un exemple de bloc PL/SQL anonyme qui permet de ramener des données du passé vers le présent.

 
Sélectionnez
DECLARE
  CURSOR emp_before IS
    SELECT sal FROM emp WHERE ename = 'KING';
  CURSOR emp_now IS
    SELECT sal FROM emp WHERE ename = 'KING';
  v_sal_before NUMBER;
  v_sal_now    NUMBER;
BEGIN
  dbms_flashback.enable_at_time(SYSDATE - (30 / 1440));
  OPEN emp_before;
  dbms_flashback.disable;
  FETCH emp_before
    INTO v_sal_before;
  CLOSE emp_before;
  OPEN emp_now;
  FETCH emp_now
    INTO v_sal_now;
  CLOSE emp_now;
  dbms_output.put_line('Sal Before : ' || v_sal_before);
  dbms_output.put_line('Sal Now : ' || v_sal_now);
END;

Voici le résultat :

 
Sélectionnez
Sal Before : 5000
Sal Now : 6000

Cette fonctionnalité nous permettrait de corriger tout DELETE ou mauvais UPDATE.

Dans cet exemple on supposera qu'un mauvais update a été effectué avec la commande suivante :

 
Sélectionnez
UPDATE emp SET sal=0;
COMMIT;

SQL> SELECT *
  2  FROM emp;

EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7369 SMITH      CLERK      7902 17/12/1980       0,00               20
 7499 ALLEN      SALESMAN   7698 20/02/1981       0,00    300,00     30
 7521 WARD       SALESMAN   7698 22/02/1981       0,00    500,00     30
 7566 JONES      MANAGER    7839 02/04/1981       0,00               20
 7654 MARTIN     SALESMAN   7698 28/09/1981       0,00   1400,00     30
 7698 BLAKE      MANAGER    7839 01/05/1981       0,00               30
 7782 CLARK      MANAGER    7839 09/06/1981       0,00               10
 7788 SCOTT      ANALYST    7566 19/04/1987       0,00               20
 7839 KING       PRESIDENT       17/11/1981       0,00               10
 7844 TURNER     SALESMAN   7698 08/09/1981       0,00      0,00     30
 7876 ADAMS      CLERK      7788 23/05/1987       0,00               20
 7900 JAMES      CLERK      7698 03/12/1981       0,00               30	
 7902 FORD       ANALYST    7566 03/12/1981       0,00               20
 7934 MILLER     CLERK      7782 23/01/1982       0,00               10

14 rows selected

Lançons maintenant la procédure de restauration des salaires (attention, étant donné que nous utilisons la méthode basée sur le temps il faudra respecter un délai de plus ou moins 5 minutes entre chaque actions, nous verrons ensuite comment se baser sur le numéro de SCN)

 
Sélectionnez
DECLARE
  CURSOR emp_before IS
    SELECT * FROM emp WHERE ename = 'KING';
  rec emp%ROWTYPE;
BEGIN
  dbms_flashback.enable_at_time(SYSDATE - (1 / 1440));
  OPEN emp_before;
  dbms_flashback.disable;
  LOOP
    FETCH emp_before
      INTO rec;
    EXIT WHEN emp_before%NOTFOUND;
    UPDATE emp SET sal = rec.sal WHERE empno = rec.empno;
    COMMIT;
  END LOOP;
  CLOSE emp_before;
EXCEPTION
  WHEN OTHERS THEN
    dbms_flashback.disable;
END;

Regardons maintenant le résultat de notre action :

 
Sélectionnez
EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7369 SMITH      CLERK      7902 17/12/1980     800,00               20
 7499 ALLEN      SALESMAN   7698 20/02/1981    1600,00    300,00     30
 7521 WARD       SALESMAN   7698 22/02/1981    1250,00    500,00     30
 7566 JONES      MANAGER    7839 02/04/1981    2975,00               20
 7654 MARTIN     SALESMAN   7698 28/09/1981    1250,00   1400,00     30
 7698 BLAKE      MANAGER    7839 01/05/1981    2850,00               30
 7782 CLARK      MANAGER    7839 09/06/1981    2450,00               10
 7788 SCOTT      ANALYST    7566 19/04/1987    3000,00               20
 7839 KING       PRESIDENT       17/11/1981    5000,00               10
 7844 TURNER     SALESMAN   7698 08/09/1981    1500,00      0,00     30
 7876 ADAMS      CLERK      7788 23/05/1987    1100,00               20
 7900 JAMES      CLERK      7698 03/12/1981     950,00               30
 7902 FORD       ANALYST    7566 03/12/1981    3000,00               20
 7934 MILLER     CLERK      7782 23/01/1982    1300,00               10

14 rows selected

Nous avons donc pu récupérer nos informations.

IV-E. Ramener des données du passé au présent en utilisant un SCN

Nous vu comment fonctionnait le FLASHBACK avec une date. Le problème de cette méthode est la grande incertitude de notre position dans le temps, car nous avons une marge de plus ou moins 5 minutes par rapport à la date que nous avons demandée.

Par exemple on suppose que le SCN 1000 correspond à l'heure 8:41 et que le SCN 1005 correspond à l'heure 8:46. Si l'on tentait de faire un retour dans le temps entre les 8:41:00 et 8:45:59 alors Oracle se baserait sur le SCN 1000, alors qu'un FLASHBACK pour 8:46 sera basé sur le SCN 1005. Ce qui signifie que nous avons une marge d'erreur de 5 SCN.
Bien que cette méthode soit plus intéressante, il vous faudra stocker de manière indépendante les N° de SCN qui nous intéresserons. De plus dans cet exemple nous utiliserons une FLASHBACK QUERY avec une boucle FOR (ce qui simplifiera le code).

Pour récupérer le n° de SCN courant il vous suffira d'utiliser la fonction dbms_flashback.get_system_change_number (renvoie le SCN dans un NUMBER).

On crée une table qui sert à sauvegarder les numéros de SCN :

 
Sélectionnez
create table save_scn (save_scn number);

Ensuite on lance une simulation d'action :

 
Sélectionnez
DECLARE
  v_scn NUMBER;
BEGIN
  v_scn := dbms_flashback.get_system_change_number;
  INSERT INTO save_scn VALUES (v_scn);
  COMMIT;
END;
/
BEGIN
  DELETE FROM emp WHERE ename = 'KING';
  COMMIT;
  DELETE emp WHERE ename = 'ADAMS';
  COMMIT;
  -- On fait un UPDATE qui lui devra être conservé.
  UPDATE emp SET sal = 1500 WHERE ename = 'JAMES';
  COMMIT;
END;
/

Puis on lance notre récupération :

 
Sélectionnez
DECLARE
  CURSOR emp_before IS
    SELECT * FROM emp;
  CURSOR emp_now(v_empno NUMBER) IS
    SELECT empno FROM emp WHERE empno = v_empno;
  v_is_present NUMBER;
  v_scn        NUMBER;
BEGIN
  -- On récupère le  de scn
  SELECT save_scn INTO v_scn FROM save_scn;
  dbms_output.put_line('Version Actuelle');
  FOR rec IN (SELECT empno, ename, sal FROM emp) LOOP
    dbms_output.put(rec.empno);
    dbms_output.put(' ' || rec.ename);
    dbms_output.put_line(' ' || rec.sal);
  END LOOP;
  -- Maintenant on veut revenir en arrière.
  dbms_flashback.enable_at_system_change_number(query_scn => v_scn);
  -- Voici aussi un exemple d'utilisation avec une BOUCLE FOR
  FOR rec IN emp_before LOOP
    -- On reviens dans le présent
    dbms_flashback.disable;
    -- On regarde si la ligne existe dans la table du présent
    OPEN emp_now(rec.empno);
    FETCH emp_now
      INTO v_is_present;
    CLOSE emp_now;
    -- Si elle n'est pas présente on fait un insert
    IF v_is_present IS NULL THEN
      INSERT INTO emp
      VALUES
        (rec.empno, rec.ename, rec.job, rec.mgr, rec.hiredate, rec.sal, rec.comm, rec.deptno);
      COMMIT;
    END IF;
    v_is_present := NULL;
  END LOOP;
  dbms_output.put_line('');
  dbms_output.put_line('');
  dbms_output.put_line('Nouvelle Version');
  FOR rec IN (SELECT empno, ename, sal FROM emp) LOOP
    dbms_output.put(rec.empno);
    dbms_output.put(' ' || rec.ename);
    dbms_output.put_line(' ' || rec.sal);
  END LOOP;
END;
/

Et voici le résultat :

 
Sélectionnez
Version Actuelle
7369 SMITH 800
7499 ALLEN 1600
7521 WARD 1250
7566 JONES 2975
7654 MARTIN 1250
7698 BLAKE 2850
7782 CLARK 2450
7788 SCOTT 3000
7844 TURNER 1500
7900 JAMES 1500
7902 FORD 3000
7934 MILLER 1300


Nouvelle Version
7369 SMITH 800
7499 ALLEN 1600
7521 WARD 1250
7566 JONES 2975
7654 MARTIN 1250
7698 BLAKE 2850
7782 CLARK 2450
7788 SCOTT 3000
7839 KING 5000
7844 TURNER 1500
7876 ADAMS 1100
7900 JAMES 1500
7902 FORD 3000
7934 MILLER 1300

On constate que nos deux lignes sont bien revenues et que le salaire de JAMES n'a pas été modifié.

V. Annexes

Pour pouvoir consulter ces documents vous devez avoir un compte sur OTN (Oracle Technology Network) valide.
Vous pouvez en créer un gratuitement en suivant ce lien

Découvrez les autres nouveautés d'Oracle 9i avec l'article de SheikYerbouti
http://sheikyerbouti.developpez.com/evol_9i/

VI. Remerciements

Merci à toute l'équipe de Développez et à SheikYerbouti.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2004 Helyos. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.