Rechercher sur arkzoyd.com

29 août 2007

Kill Session et 11g RAC

Une nouvelle syntaxe de la commande ALTER SYSTEM KILL SESSION permet théoriquement de supprimer une session sur une autres instance d'un RAC ; en plus des paramètres sid et
serial#, ajoutez le numéro de l'instance que vous pouvez trouver dans la vue GV$SESSION pour tuer une session RAC-widen; Voici un exemple d'utilisation :
  • Connectez-vous avec SQL*Plus a une instance RAC :
sqlplus scott/tiger
  • A parir d'une autre instance repérez la connexion sur l'autre noeud avec la commande suivante :
sqlplus / as sysdba

col sid format 999
col inst_id format 9
col serial# format 99999
select sid,
serial#,
inst_id
from gv$session
where program like 'sqlplus%'
and username='SCOTT';

SID SERIAL# INST_ID
--- ------- -------
170 7 1
  • Tuez la session que vous avez repéré :
alter system kill session '170,7,1';
  • Dommage, ça ne marche pas sur une single instance ; j'espère, qu'au moins, ça marche avec RAC :
alter system kill session '170,7,1';

alter system kill session '170, 7, 1'
*
ERROR at line 1:
ORA-00026: missing or invalid session ID
Bon... J'ai les CD, la plate-forme et un long Week-End. J'aurais bientôt installé RAC 11g !

Pour votre culture : Lundi est férié dans mon nouveau pays ; c'est sans doute pour la Saint Grégory ! Les canadiens sont vraiment accueillant : C'est quand même mieux que ce qu'on fait aux Gregory en France, non ?

PIVOT et UNPIVOT

La clause PIVOT permet d'afficher les lignes d'une requête sous forme de colonnes à la manière des tableaux croisés dynamique d'Excel. Le mieux, pour comprendre, c'est de partir d'un exemple.

Voici une requête qui ramène par département et par "job", la somme des salaires de la table SCOTT.EMP :
select deptno, job, sum(sal) sal
from emp
group by deptno, job;

DEPTNO JOB SAL
---------- --------- ----------
20 CLERK 1900
30 SALESMAN 5600
20 MANAGER 2975
30 CLERK 950
10 PRESIDENT 5000
30 MANAGER 2850
10 CLERK 1300
10 MANAGER 2450
20 ANALYST 6000
Mettons que vous vouliez avoir un tableau avec, une ligne pour chaque job et une colonne pour chaque numéro de département ; la somme des salaires doit être l'intersection de chaque ligne et colonne... Le tableau que vous voulez ressemble à ceci :
JOB           DEPT10     DEPT20     DEPT30
--------- ---------- ---------- ----------
CLERK 1300 1900 950
SALESMAN 5600
PRESIDENT 5000
MANAGER 2450 2975 2850
ANALYST 6000
En 10g, on peut s'en sortir avec les fonctions DECODE ou CASE ! En 11g, la clause PIVOT rend la requête facile à écrire :
WITH qry AS (select deptno, job, sum(sal) sal
from emp
group by deptno, job)
select * from qry
pivot (sum(sal) for deptno in
(10 as dept10, 20 as dept20, 30 as dept30));
Vous remarquerez qu'avec l'affichage en SQL, on est obligé de préciser toutes les valeurs des départements (le texte en bleu). On aimerait bien pouvoir dire quelque chose comme ceci :
select * from qry
pivot (sum(sal) for deptno in
(select deptno from dept));
Malheureusement, ce n'est pas (encore ?) possible. Toutefois, si vous pouvez afficher ou manipuler le résultat en XML avec une feuille de style ou un langage de programmation, la syntaxe devient possible et vous pouvez ne pas supposer le nombre de lignes ou de colonnes :
set long 300
with qry as (select deptno, job, sum(sal) sal
from emp
group by deptno, job)
select job,deptno_xml from qry
pivot xml (sum(sal) for deptno in (any));

JOB DEPTNO_XML
--------- --------------------------------------------
ANALYST <PivotSet>
<item>
<column name = "DEPTNO">20</column>
<column name = "SUM(SAL)">6000</column>
</item>
</PivotSet>

CLERK <PivotSet>
<item>
<column name = "DEPTNO">10</column>
<column name = "SUM(SAL)">1300</column>
</item>
<item>
<column name = "DEPTNO">20</column>
<column name = "SUM(SAL)">1900</column>
</item>
<item>
<column name = "DEPTNO">30</column>
<column name = "SUM(SAL)">950</column>
</item>
</PivotSet>
[...]
Si vous voulez, vous pouvez aussi générer tout le contenu dans un seul fichier XML avec les opérateurs XML de la base de données :
set long 3000
select sys_xmlagg(myrow) as "RESULT" from (
with qry as (select deptno, job, sum(sal) sal
from emp
group by deptno, job)
select xmlelement("ROW",
xmlforest(job,deptno_xml)) as "MYROW" from qry
pivot xml (sum(sal) for deptno in (any)));


RESULT
-----------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
<ROW>
<JOB>ANALYST</JOB>
<DEPTNO_XML>
<PivotSet>
<item>
<column name="DEPTNO">20</column>
<column name="SUM(SAL)">6000</column>
</item>
</PivotSet>
</DEPTNO_XML>
</ROW>
<ROW>
<JOB>CLERK</JOB>
<DEPTNO_XML>
<PivotSet>
<item>
<column name="DEPTNO">10</column>
<column name="SUM(SAL)">1300</column>
</item>
<item>
<column name="DEPTNO">20</column>
<column name="SUM(SAL)">1900</column>
</item>
<item>
<column name="DEPTNO">30</column>
<column name="SUM(SAL)">950</column>
</item>
</PivotSet>
</DEPTNO_XML>
</ROW>
<ROW>
<JOB>MANAGER</JOB>
<DEPTNO_XML>
<PivotSet>
<item>
<column name="DEPTNO">10</column>
<column name="SUM(SAL)">2450</column>
</item>
<item>
<column name="DEPTNO">20</column>
<column name="SUM(SAL)">2975</column>
</item>
<item>
<column name="DEPTNO">30</column>
<column name="SUM(SAL)">2850</column>
</item>
</PivotSet>
</DEPTNO_XML>
</ROW>
<ROW>
<JOB>PRESIDENT</JOB>
<DEPTNO_XML>
<PivotSet>
<item>
<column name="DEPTNO">10</column>
<column name="SUM(SAL)">5000</column>
</item>
</PivotSet>
</DEPTNO_XML>
</ROW>
<ROW>
<JOB>SALESMAN</JOB>
<DEPTNO_XML>
<PivotSet>
<item>
<column name="DEPTNO">30</column>
<column name="SUM(SAL)">5600</column>
</item>
</PivotSet>
</DEPTNO_XML>
</ROW>
</ROWSET>
le contraire, à savoir transformer un tableau en ligne, est également possible ! La clause UNPIVOT permet par exemple de regrouper les colonnes SAL et COMM de la table SCOTT.EMP et de spécifier une valeur dans la colonne "TYPE" pour les différencier :
col EMPNO format 9999
select empno,
ename,
type,
sal_comm
from emp
unpivot (sal_comm
for type in (SAL as 'Salary',
COMM as 'Commission'));

EMPN ENAME TYPE SAL_COMM
---- ---------- ---------- ----------
7369 SMITH Salary 800
7499 ALLEN Salary 1600
7499 ALLEN Commission 300

7521 WARD Salary 1250
7521 WARD Commission 500
7566 JONES Salary 2975
7654 MARTIN Salary 1250
7654 MARTIN Commission 1400
7698 BLAKE Salary 2850
7782 CLARK Salary 2450
7788 SCOTT Salary 3000
7839 KING Salary 5000
7844 TURNER Salary 1500
7844 TURNER Commission 0
7876 ADAMS Salary 1100
7900 JAMES Salary 950
7902 FORD Salary 3000
7934 MILLER Salary 1300
Comme vous pouvez le voir dans l'exemple ci-dessus, les lignes de la table d'origine ont été dupliquées pour contenir les différentes valeurs des colonnes sur lesquelles le UNPIVOT est appliqué. Les colonnes ayant des valeurs NULL ne génèrent pas, quant à elles, de lignes supplementaires.

17 août 2007

44 jours pour donner votre avis...

Peut-être que vous tenez-là une des raisons de mon départ outre-Atlantique ?

Qu'est-ce qu'il y a dans le fichier de contrôle ?

Pour ceux qui se posent la question :
select type, records_used, records_total
from v$controlfile_record_section;

TYPE RECORDS_USED RECORDS_TOTAL
---------------------------- ------------ -------------
DATABASE 1 1
CKPT PROGRESS 0 11
REDO THREAD 1 8
REDO LOG 3 16
DATAFILE 5 100
FILENAME 9 2298
TABLESPACE 6 100
TEMPORARY FILENAME 1 100
RMAN CONFIGURATION 0 50
LOG HISTORY 41 292
OFFLINE RANGE 0 163
ARCHIVED LOG 25 28
BACKUP SET 0 409
BACKUP PIECE 0 200
BACKUP DATAFILE 0 245
BACKUP REDOLOG 0 215
DATAFILE COPY 1 200
BACKUP CORRUPTION 0 371
COPY CORRUPTION 0 409
DELETED OBJECT 1 818
PROXY COPY 0 246
BACKUP SPFILE 0 131
DATABASE INCARNATION 2 292
FLASHBACK LOG 0 2048
RECOVERY DESTINATION 1 1
INSTANCE SPACE RESERVATION 1 1055
REMOVABLE RECOVERY FILES 0 1000
RMAN STATUS 0 141
THREAD INSTANCE NAME MAPPING 8 8
MTTR 1 8
DATAFILE HISTORY 0 57
STANDBY DATABASE MATRIX 10 10
GUARANTEED RESTORE POINT 0 2048
RESTORE POINT 0 2083
DATABASE BLOCK CORRUPTION 0 8384
ACM OPERATION 4 64
FOREIGN ARCHIVED LOG 0 1002

Estimer la taille d'un index avant de le créer

Supposons que vous vouliez créer un index comme ceci :
create index emp_zz on emp(empno,sal,comm)
Vous pouvez estimer la taille de l'index et la taille du segment grâce au package DBMS_SPACE comme ci-dessous :
var v_used_bytes number
var v_alloc_bytes number
var v_sql char(50)
exec :v_sql:='create index emp_zz on emp(empno,sal,comm)'
exec dbms_space.create_index_cost(:v_sql, -
:v_used_bytes, -
:v_alloc_bytes)
print :v_used_bytes
print :v_alloc_bytes

V_USED_BYTES
------------
140

V_ALLOC_BYTES
-------------
65536
Une nouveauté 10g (oui 10g !) #CQFD

Créer un SPFILE ... depuis la mémoire

La nouvelle syntaxe qui suit permet de créer un SPFILE depuis les paramètres actuellement utilisés par l'instance ou les instances si vous utilisez RAC :
create spfile='/tmp/spfileORCL.ora'
from memory;
Vous pouvez également générer un fichier de paramètre sous le format texte avec la commande suivante :
create pfile='/tmp/initORCL.ora'
from memory;
D'ici quelques jours j'aurai installé le clusterware 11g... Je vous en dirai plus !

11g : Enregistrer les appels PL/SQL dans les redologs

Il y a deux ou trois trucs que je n'arrive toujours pas à faire fonctionner avec 11g ; la faute à mon manque de concentration sans doute ! Ajouter les appels PL/SQL dans les redo logs en fait visiblement parti et cette fois, il va falloir m'aider...

Voilà l'histoire : 11g permet théoriquement d'ajouter en "SUPPLEMENTAL LOG" les appels aux procédures PL/SQL. Selon ce que j'en comprends, cette fonctionnalité pourrait permettre de copier la réplication procédurale via Streams et ainsi (enfin ?) d'abandonner Oracle "Advanced Replication Option" que j'ai payé au propre comme au figuré dans une autre vie.

Le paramétrage est théoriquement simple et s'exécute en quelques minutes ; le script ci dessous passe la base de données en mode archivelog et active cette fonctionnalité :
sqlplus / as sysdba

shutdown immediate

startup mount

alter system set
log_archive_dest_1=
'LOCATION=/u02/app/oracle/oradata/ORCL/archives';

show parameter log_archive_format

NAME TYPE VALUE
------------------ ------ ------------
log_archive_format string %t_%s_%r.dbf

alter database archivelog;

alter database
add supplemental log
data for procedural replication;

select supplemental_log_data_pl
from v$database;

SUP
---
YES

alter database open;
Ça se complique quand il s'agit de vérifier que ça marche bien :

Option 1 : Utiliser LogMiner pour savoir si l'appel à la procédure est tracé dans le fichier redo log

On procède en 3 étapes et on utilise Log Miner en stockant le dictionnaire dans le redo log :
  • Enregistrer le dictionnaire dans les redo logs et notez la première archive utile :
EXECUTE DBMS_LOGMNR_D.BUILD( -
OPTIONS=> DBMS_LOGMNR_D.STORE_IN_REDO_LOGS);

SELECT NAME FROM V$ARCHIVED_LOG WHERE sequence# in (
select max(sequence#) from V$ARCHIVED_LOG where DICTIONARY_BEGIN='YES');

NAME
--------------------------------------------------------
/u02/app/oracle/oradata/ORCL/archives/1_38_630213449.dbf
  • Générer l'appel à la procédure que l'on va essayer de retrouver. Sous le schéma SCOTT, exécutez les opérations suivantes (Les ordres insert et delete permettent de delimiter la zone du Redo dans laquelle on recherchera l'appel à la procédure) :
connect scott/tiger

create or replace procedure gark(a number) is
begin
null;
end;
/

sqlplus scott/tiger

select sid, serial#
from v$session
where audsid=SYS_CONTEXT('USERENV', 'SESSIONID');

SID SERIAL#
--- ----------
125 146


insert into dept values (17,'GG','GG');

commit;

call gark(10);

delete from dept where deptno=17;

commit;

alter system archive log current;

  • Enregistrer ensuite les fichiers à examiner avec Log Miner et interroger la vue V$LOGMNR_CONTENTS pour retrouver les informations avec LogMiner :
select name
from v$archived_log
where sequence# in (select max(sequence#)
from v$archived_log
where dest_id=1)
and dest_id=1;

NAME
--------------------------------------------------------
/u02/app/oracle/oradata/ORCL/archives/1_39_630213449.dbf

EXECUTE DBMS_LOGMNR.ADD_LOGFILE( -
LOGFILENAME => -
'/u02/app/oracle/oradata/ORCL/archives/1_38_630213449.dbf',-
OPTIONS => DBMS_LOGMNR.NEW);

EXECUTE DBMS_LOGMNR.ADD_LOGFILE( -
LOGFILENAME => -
'/u02/app/oracle/oradata/ORCL/archives/1_39_630213449.dbf');

select filename from V$LOGMNR_LOGS;

FILENAME
--------------------------------------------------------
/u02/app/oracle/oradata/ORCL/archives/1_38_630213449.dbf
/u02/app/oracle/oradata/ORCL/archives/1_39_630213449.dbf


EXECUTE DBMS_LOGMNR.START_LOGMNR(-
OPTIONS => DBMS_LOGMNR.DICT_FROM_REDO_LOGS)

accept sid prompt "Enter Session ID: "
125
accept serial prompt "Enter Session Serial#: "
146
col operation format a8
select scn,
operation,
object_id
session#,
serial#,
redo_value, username
from v$logmnr_contents
where session#=&sid
and serial#=&serial;

SCN OPERATIO SESSION# SERIAL# REDO_VALUE
---------- -------- ---------- ---------- ----------
1090276 START 125 146 19070
1090276 INSERT 125 146 19072
1090278 COMMIT 125 146 19074
1090292 START 125 146 19076
1090278 COMMIT 125 146 19074
1090292 DELETE 125 146 19078
1090292 INTERNAL 125 146 19080
1090294 COMMIT 125 146 19082

exec DBMS_LOGMNR.END_LOGMNR
Après plus d'investigation, il ne semble pas que ni l'opération INTERNAL ni aucune autre operation entre l'insert et le delete ne corresponde à l'appel de la procédure.

Option 2 : "Dumper" le fichier LOG


Vous pouvez générer un DUMP du fichier log :
alter system dump logfile
'/u02/app/oracle/oradata/ORCL/archives/1_39_630213449.dbf';
Là encore je n'ai pas trouvé non plus malgré cet excellent papier. Le fichier est quant à lui dans le sous-repertoire >diagnostic_dest</diag/rdbms/>DB_NAME</>INST_NAME</trace

Avant de conclure

désactivez la clause de SUPPLEMENTAL LOG et éventuellement le mode ARCHIVELOG avec le script ci-dessous :
alter database
drop supplemental log
data for procedural replication;

select supplemental_log_data_pl
from v$database;

SUP
---
NO

shutdown immediate;

startup mount;

alter database noarchivelog;
alter database open;
Conclusion :

Mauvais jour pour moi ; je réessaierai plus tard mais sans doute que vous aurez trouvé avant moi. Si c'est le cas, merci de me l'indiquer ;). Ce que je me demande encore c'est :
  • Est-ce qu'il ne faut pas faire autre chose comme marquer la fonction avec une procédure PL/SQL comme c'est le cas avec la réplication procédurale actuelle ?
  • Est-ce que j'ai les yeux en face des trous et je n'aurais pas laissé l'ordre dans le dump du redolog ?
  • En l'état, est-ce que cette fonctionnalité a un intérêt quelconque ?
  • Est-ce que tout simplement ce n'est pas une lacune de la version actuelle ?
Pour en savoir plus et faire fonctionner cette fonctionnalité, la documentation manque, elle aussi, parfois, de précision :

SQL Developer et la "Price List" sont dehors !

Le premier patch de SQL Developer 1.2 est dehors. Ce n'est semble-t-il qu'un correctif. D'après les dernières nouvelles, la Plug-In SQL*Developer que nous avons développée pourrait, elle aussi, avoir reçue une correction ;). Je suis en plein déménagement pour le nouveau monde (définitif cette fois !) alors merci de patienter quelques jours.

D'autre part, la très attendue liste de prix d'Oracle 11g est également disponible : pas de surprise, c'est conforme à ce que The Register, Yahoo! et moi annoncions !

Je n'arrive pas à retrouver la dépendance entre Tuning et Diagnostic Pack. D'ou la question : "N'y a-t-il pas de dépendance entre Real Application Testing et Tuning Pack dans la mesure où il faut accéder aux SQL Tuning Set (STS) pour utiliser SQL Performance Analyzer comme vous pouvez le voir dans ce post précédent à propos de SQLPA ?". Si quelqu'un arrive à trouver la réponse à cette question existentielle, je suis intéressé aussi.

16 août 2007

Les index invisibles de la 11g

Dans un post précédent "Index ou presque", j'illustrais comment utiliser un index sans le créer grâce à la clause NOSEGMENT. 11g offre la capacité contraire à savoir de créer un index sans que celui-ci soit utilisé par l'optimiseur à moins que vous ne spécifiez que la session peut utiliser ce type d'index. On a tous, un jour ou l'autre, créé un index qui a dégradé les performances d'une de nos requêtes.

L'exemple qui suit illustre comment manipuler un index invisible et, peut-être, comment éviter un prochain problème. D'abord créez une table et ajoutez quelques lignes :
create table gark(id number);

begin
for i in 1..10000 loop
insert into gark values(i);
end loop;
commit;
end;
/
Vous pouvez interroger la table à l'aide de la requête qui suit :
select id
from gark
where id=10;

select *
from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID 4wctq8rj3ryva, child number 1
-------------------------------------
select id from gark where id=10

Plan hash value: 2884726894
---------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 7 |
|* 1 | TABLE ACCESS FULL| GARK | 1 | 13 | 7 |
---------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("ID"=10)

Note
-----
- dynamic sampling used for this statement
Maintenant créez l'index avec la clause INVISIBLE :
create index gark_idx
on gark(id) invisible;

Index created.
Si vous exécutez la requête à nouveau l'index n'est pas utilisé, malgré sa pertinence pour la requête :
select id
from gark
where id=10;

select *
from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
----------------------------------------
SQL_ID 4wctq8rj3ryva, child number 1
-------------------------------------
select id from gark where id=10

Plan hash value: 2884726894
---------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 7 |
|* 1 | TABLE ACCESS FULL| GARK | 1 | 13 | 7 |
---------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("ID"=10)

Note
-----
- dynamic sampling used for this statement
Si par contre vous positionnez la valeur du paramètre OPTIMIZER_USE_INVISIBLE_INDEXES à true au niveau de la session et que vous ré-exécutez la requête l'index est utilisé :
alter session set
optimizer_use_invisible_indexes=true;

select id
from gark
where id=10;

select *
from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID 4wctq8rj3ryva, child number 1
-------------------------------------
select id from gark where id=10

Plan hash value: 280029042
-----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost|
-----------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1 |
|* 1 | INDEX RANGE SCAN| GARK_IDX | 1 | 13 | 1 |
-----------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("ID"=10)
Note
-----
- dynamic sampling used for this statement
Une requête pour vérifiez si l'index est visible :
select index_name, visibility
from user_indexes
where index_name='GARK_IDX';

INDEX_NAME VISIBILITY
---------- ----------
GARK_IDX INVISIBLE
Vous pouvez rentre un index visible avec ALTER INDEX :
alter index gark_idx visible;

select index_name, visibility
from user_indexes
where index_name='GARK_IDX';

INDEX_NAME VISIBILITY
---------- ----------
GARK_IDX VISIBLE
Pour terminer, supprimez la table et l'index de l'exemple :
drop table gark
cascade constraints
purge;
Pour en savoir plus sur les index invisibles, référez-vous à la section correspondante de Administrator's Guide.

Balade dans les "colonnes virtuelles" d'Oracle 11g

Vous connaissez la table EMP du schema SCOTT !
desc EMP

Name Null? Type
-------- -------- ------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)
11g vous permet d'y ajouter, comme à toutes vos tables, des colonnes virtuelles. Pour cela, utilisez la clause "GENERATED ALWAYS AS" ou plus simplement "AS" après avoir specifié le type de données de votre colonne comme ci-dessous :
alter table emp add
sal_comm number
generated always as (sal+nvl(comm,0));

Table altered.
Votre table à maintenant une colonne en plus :
desc EMP

Name Null? Type
-------- -------- ------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)
SAL_COMM NUMBER
Et vous pouvez simplement l'interroger comme n'importe quelle colonne :
select empno,ename,sal_comm
from emp
where sal_comm>2900
/

EMPNO ENAME SAL_COMM
----- ---------- ----------
7566 JONES 2975
7788 SCOTT 3000
7839 KING 5000
7902 FORD 3000

select * from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID 0vxq332b0gmaz, child number 0
-------------------------------------
select empno,ename,sal_comm from emp where sal_comm>2900

Plan hash value: 3956160932

---------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 29 | 3 |
---------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SAL"+NVL("COMM",0)>2900)
Par contre, comme vous pouvez vous y attendre, vous ne pouvez pas la mettre à jour :
SQL> update emp set sal_comm=0;
update emp set sal_comm=0
*
ERROR at line 1:
ORA-54017: UPDATE operation disallowed on virtual columns
Vous pouvez l'indexer ; l'index créé est un index fonction :
create index emp_sal_comm
on emp(sal_comm);

Index created.

col INDEX_NAME format a12
col INDEX_TYPE format a21
select index_name, index_type, table_owner
from user_indexes
where index_name='EMP_SAL_COMM';

INDEX_NAME INDEX_TYPE TABLE_OWNER
------------ --------------------- -----------
EMP_SAL_COMM FUNCTION-BASED NORMAL SCOTT
Si vous voulez réaliser un calcul plus complexe, vous pouvez toujours créer un package ou une fonction et l'intégrer dans votre colonne virtuelle. Ne faites pas ce qui suit bien sur ! c'est juste pour l'exemple !
create or replace function dept_name(p_deptno number)
return varchar2 deterministic is
v_out varchar2(50);
begin
select dname into v_out
from dept where deptno=p_deptno;
return v_out;
end;
/

alter table emp add dname varchar2(50) as
(cast (dept_name(deptno) as varchar2(50)));
Enfin, et ça peut être le plus utile pour résoudre une requête associée à cette colonne virtuelle, vous pouvez prendre des statistiques sur cette colonne virtuelle ; j'ai déjà montré dans un post précédent que les statistiques sur des colonnes virtuelles peuvent changer un plan et les performances d'une requête :
select count(ENDPOINT_NUMBER)-1 NUM_BUCKETS
from user_histograms
where table_name='EMP'
and COLUMN_NAME='SAL_COMM';

NUM_BUCKETS
-----------
-1

exec dbms_stats.gather_table_stats(user,'EMP', -
method_opt=>'FOR COLUMNS SAL_COMM SIZE 254');

PL/SQL procedure successfully completed.

select count(ENDPOINT_NUMBER)-1 NUM_BUCKETS
from user_histograms
where table_name='EMP' and COLUMN_NAME='SAL_COMM';

NUM_BUCKETS
-----------
12
Avant de partir, laissez le schéma SCOTT comme vous l'avez trouvé :
alter table emp drop column dname;
alter table emp drop column sal_comm;
Pour en savoir plus sur toutes ces fonctionnalités, reportez-vous à la documentation et en particulier :

Superviser les ordres DDL en 11g

Pour être honnête, c'est Howard J. Rogers et ses articles à propos des nouveaux paramètres d'initialisation (1,2 et 3 pour l'instant !) qui inspirent ce post...

En général une application ne génère pas de DDL (LDD, si vous préférez ?); il s'agit le plus souvent d'opérations manuelles... Savoir ce qu'a fait un autre DBA, ou un développeur, sur une base de données en production peut-être très utile. Vous pouvez sans doute mettre en oeuvre l'audit mais ce n'est pas forcément des plus spontanés pour un DBA face à un problème ! Le paramètre enable_ddl_logging permet d'enregistrer les ordres DDL dans le fichier alert.log; voici une démonstration de cette fonctionnalité :

Connectez-vous DBA et exécutez la série de commandes ci-dessous :
alter system set enable_ddl_logging=TRUE;
alter table scott.emp add gcol number;
alter table scott.emp drop column gcol;
alter system set enable_ddl_logging=FALSE;

exit
Visualisez ensuite le fichier alert.log avec la commande ci-dessous :
adrci exec="show alert"
Les lignes qui suivent apparaissent dans le fichier alert.log :
ALTER SYSTEM SET enable_ddl_logging=TRUE SCOPE=BOTH;
alter table scott.emp add gcol number
alter table scott.emp drop column gcol
ALTER SYSTEM SET enable_ddl_logging=FALSE SCOPE=BOTH;

15 août 2007

Real Application Testing (It's not RAC !) /*+ Part 2 */

SQL Performance Analyzer (SQLPA) est l'autre bonne nouvelle de Real Application Testing Option (cf mon Post précédent à propos de Database Replay). SQLPA utilise les ordres SQL stockés dans les SQL Tuning Set. Il permet de conserver plans et statistiques de ces ordres avant et après différentes variations de votre base de données. Vous pouvez, au moyen de ses rapports d'analyse, visualiser l'impact de changements sur l'activité SQL. Vous pouvez visualiser ces variations pour 1 comme pour 1000 ordres SQL aussi simplement. Cette seconde partie consacrée à l'option Real Application Testing, illustre à travers un exemple simple comment utiliser SQL Performance Analyzer.

Si vous voulez savoir comment ça marche, regardez la doc et en particulier :
Ce qui suit est un exemple simple pour illustrer ce que fait SQLPA et en quoi c'est différent, par exemple de Database Replay.

1. Schéma d'exemple

Pour exécuter l'exemple de ce Post, il faut créer une table et la remplir avec 10000 lignes, comme ci-dessous :
create table gark
(id number not null);

begin
for i in 1..10000 loop
insert into gark(id)
values (i);
end loop;
commit;
end;
/
2. Requête et plan

Avec SQL*Plus (ça ne marche pas avec SQL*Developer, parce qu'il génère des Ordres SQL entre les différentes partie de votre script), lancez le script ci-dessous :
set serveroutput off

col id format 99999
select a.id, b.id
from gark a, gark b
where a.id=b.id
and b.id=500;

select * from table(
dbms_xplan.display_cursor);
Ce script exécute une requête et affiche son plan ainsi que son SQL_ID et sa HASH_VALUE. Voici ce qu'il affiche par exemple :
-------------------------------------
SQL_ID  683kdkrs2dmrk, child number 0
-------------------------------------
select a.id, b.id   from gark a, gark b  where a.id=b.id 
and b.id=500

Plan hash value: 2625395012

----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |    15 (100)|
|*  1 |  HASH JOIN         |      |     1 |    26 |    15   (7)|
|*  2 |   TABLE ACCESS FULL| GARK |     1 |    13 |     7   (0)|
|*  3 |   TABLE ACCESS FULL| GARK |     1 |    13 |     7   (0)|
----------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."ID"="B"."ID")
2 - filter("A"."ID"=500)
3 - filter("B"."ID"=500)

Note
-----
- dynamic sampling used for this statement
3. Capturer la requête dans un SQL Tuning Set

Une fois la requête dans la Shared pool, vous pouvez creer un SQL Tuning Set et l'enregistrer dans ce STS. Voici comment procéder
  • D'abord, créez le SQL Tuning Set
BEGIN
DBMS_SQLTUNE.CREATE_SQLSET(
sqlset_name => 'gark_sts',
description => 'STS for SPA demo');
END;
/
  • Ensuite, chargez la requête précédente dans le SQL Tuning set avec la procédure qui suit :
accept sql_id prompt "Enter value for sql_id: "
683kdkrs2dmrk

DECLARE
l_cursor  DBMS_SQLTUNE.sqlset_cursor;
BEGIN
OPEN l_cursor FOR
SELECT VALUE(p)
FROM   TABLE (
DBMS_SQLTUNE.select_cursor_cache (
'sql_id = ''&sql_id''', -- basic_filter
NULL, -- object_filter
NULL, -- ranking_measure1
NULL, -- ranking_measure2
NULL, -- ranking_measure3
NULL, -- result_percentage
1)    -- result_limit
) p;
DBMS_SQLTUNE.load_sqlset (
sqlset_name    => 'gark_sts',
populate_cursor => l_cursor);
END;
/

PL/SQL procedure successfully completed.
  • Vérifiez que l'ordre SQL est bien dans le STS à l'aide de la requête ci-dessous :
col sql format a50
set lines 120
SELECT sql_id,
substr(sql_text, 1, 50) sql
FROM TABLE(
DBMS_SQLTUNE.select_sqlset (
'gark_sts'));
L'ordre SQL retourne quelque chose comme ceci :
SQL_ID        SQL
------------- ---------------------
683kdkrs2dmrk select a.id, b.id
from gark a, gark b
where a.i
4. Capturer les statistiques et les plans de l'exécution avant les changements.

Cette étape est préalable au changement et servira de base aux comparaisons ultérieures. Dans l'ordre, il faut :
  • Créer une tache d'analyse pour le SQL Performance Analyzer et y référencer le STS
var v_out char(50)
begin
:v_out:=dbms_sqlpa.create_analysis_task(
sqlset_name => 'gark_sts',
task_name   => 'gark_spa_task');
end;
/
print v_out

V_OUT
-------------
gark_spa_task


  • Vérifier que la tache est bien créée
col TASK_NAME format a14
col ADVISOR_NAME format a24
select TASK_NAME,
ADVISOR_NAME,
created
from DBA_ADVISOR_TASKS
where task_name='gark_spa_task';

TASK_NAME      ADVISOR_NAME             CREATED
-------------- ------------------------ ---------
gark_spa_task  SQL Performance Analyzer 15-AUG-07
  • Exécuter la tache d'analyse
begin
DBMS_SQLPA.EXECUTE_ANALYSIS_TASK(
task_name      => 'gark_spa_task',
execution_type => 'TEST EXECUTE',
execution_name => 'gark_spa_task_before');
end;
/
  • Vérifier l'exécution et son statut
col TASK_NAME format a20
select execution_name,
status,
execution_end
from DBA_ADVISOR_EXECUTIONS
where task_name='gark_spa_task'
order by execution_end;

EXECUTION_NAME                 STATUS      EXECUTION
------------------------------ ----------- ---------
gark_spa_task_before           COMPLETED   15-AUG-07

5. Effectuer les changements

Nous allons ajoutez un index a la table GARK
create unique index
gark_idx on gark(id);

Index created.
6. Exécuter la tache d'analyse après les changements.

Le script est le même qu'avant les changement. Seul le nom de l'exécution change
begin
DBMS_SQLPA.EXECUTE_ANALYSIS_TASK(
task_name      => 'gark_spa_task',
execution_type => 'TEST EXECUTE',
execution_name => 'gark_spa_task_after');
end;
/
  • Vérifier l'exécution et son statut
col TASK_NAME format a20
select execution_name,
status,
execution_end
from DBA_ADVISOR_EXECUTIONS
where task_name='gark_spa_task'
order by execution_end;

EXECUTION_NAME                 STATUS      EXECUTION
------------------------------ ----------- ---------
gark_spa_task_before           COMPLETED   15-AUG-07
gark_spa_task_after            COMPLETED   15-AUG-07
7. Comparer les performances avant et après les changements

Pour ce faire, vous devez créer et exécuter une tache qui effectue la comparaison avec un script comme ci-dessous :
begin
DBMS_SQLPA.EXECUTE_ANALYSIS_TASK(
task_name        => 'gark_spa_task',
execution_type   => 'COMPARE PERFORMANCE',
execution_name   => 'gark_spa_tark_compare',
execution_params => dbms_advisor.arglist(
'comparison_metric',
'buffer_gets'));
end;
/
PL/SQL procedure successfully completed.
Ensuite il vous suffit d'afficher le rapport associé à la tache à l'aide du script suivant :
variable rep CLOB;
begin
:rep := DBMS_SQLPA.REPORT_ANALYSIS_TASK(
task_name=>'gark_spa_task',
type=>'HTML',
level=>'ALL',
section=>'ALL');
end;
/
SET LONG 100000
set LONGCHUNKSIZE 100000
set LINESIZE 200
set head off
set feedback off
set echo off
spool sts_changes.html
PRINT :rep
spool off
set head on
Remarque : Le paramètre SECTION doit avoir la valeur SUMMARY ou ALL et non pas SECTION_ALL comme indiquée dans la documentation

Vous pouvez visualiser le fichier généré avec votre navigateur Web. Voici un exemple

Si vous voulez l'extrait complet, cliquez ici

8. Autre manière de visualiser les résultats de la comparaison

Vous pouvez également sélectionnez les vues suivantes pour visualiser les résultats des exécutions :

  • DBA_ADVISOR_FINDINGS
  • DBA_ADVISOR_SQLPLANS
  • DBA_ADVISOR_SQLSTATS
9. Supprimer exécutions, taches, et le schéma d'exemple

Vous pouvez supprimer toutes les informations d'exécution d'une tache (Avant les changements et apres les changements) à l'aide de la procédure reset_analysis_task.
begin
dbms_sqlpa.reset_analysis_task(task_name=>'gark_spa_task');
end;
/
col TASK_NAME format a20
select execution_name,
status,
execution_end
from DBA_ADVISOR_EXECUTIONS
where task_name='gark_spa_task'
order by execution_end;

no rows selected
Par ailleurs, vous pouvez créer autant d'exécutions que nécessaire.

Si vous voulez supprimer la tache en plus des exécutions, utilisez la procédure ci-dessous :
begin
dbms_sqlpa.drop_analysis_task(task_name=>'gark_spa_task');
end;
/

col TASK_NAME format a14
col ADVISOR_NAME format a24
select TASK_NAME,
ADVISOR_NAME,
created
from DBA_ADVISOR_TASKS
where task_name='gark_spa_task';

no rows selected
A noter également que vous pouvez :
  • Annuler l'exécution d'une tache en cours avec la procédure cancel_analysis_task
  • Interrompre l'exécution d'une tache en cours avec la procédure interrupt_analysis_task
  • Reprendre l'exécution d'une tache interrompue avec la procédure resume_analysis_task
Vous trouverez le script pour supprimer les objets utilisés dans cette démonstration ci-dessous :
drop table gark cascade constraints purge;
10. Conclusion

Vous verrez l'intérêt de SQL Performance Analyzer en créant des SQL Tuning Set avec de nombreux ordres SQL comme par exemple en capturant les données issue d'AWR. Cette approche est très différente de Database Replay et ne nécessite pas de reconstruire exactement la base de données de production.

Voila qui termine cette promenade consacrée à l'option Real Application Testing, au moins pour l'instant. Si d'autres fonctionnalités sont incluent dans la Release 2, j'y ajouterai d'autres parties. D'ici là, j'aurai le temps de discuter de nombreuses autres fonctionnalités d'Oracle 11g.

14 août 2007

Le prix ?

Un consensus (The Register, Yahoo!) semble se dégager autour du prix des nouvelles options d'Oracle 11g :
  • Real Application Testing : 10,000 USD/Processeur ou 200 USD/Named User
  • Advanced Compression : 10,000 USD/Processeur ou 200 USD/Named User
  • Total Recall : 5,000 USD/Processeur ou 100 USD/Named User
  • Active Data Guard : 5,000 USD/Processeur ou 100 USD/Named User
J'imagine que d'ici quelques heures, le site officiel contiendra la dernière "Price List". Les autres versions / options restent quant à elles au même prix. La planète blog "Oracle" semble déçue par toutes ces options et le prix associé ; Bien sur je préférerais que tout soit gratuit mais :
  • Dans ce cas la 11g aurait-elle 400 nouvelles fonctionnalités ?
  • Depuis 2000, le prix du processeur Oracle EE est 40,000 USD. Autrement dit, les options sont depuis des années le moyen pour Oracle d'augmenter ses revenus sur la base de données. Quelles seraient les conséquences pour nous si Oracle réduisaient ses profits ?

SQL Plan Management, la "killer app" de 11g

Qu'est-ce que vous préfèrez de la 11g jusqu'à présent ? Database Replay (testé dans un précédent post) qui permet de reproduire la charge de la production sur un environnement de test de manière très simple ? Snapshot Standby (également testée dans un précédent post) qui permet d'utiliser une Standby pour des tests tout en assurant la protecti0n de la base de donnees de production ? Tous les améliorations en terme de performance (algorithme NL, statistiques sur des colonnes virtuelles, statistiques multi-colonnes, bind peeking 2.0...) ? Tuning Pack Real Time SQL Monitoring ? Une des 389 nouvelles fonctionnalités que je n'ai pas encore explorée ? Ça pourrait bien être "Automatic SQL Plan Management" ! Pourquoi ?

Avec 11g Enterprise Edition, SQL Plan Management "remplace" (complète ?) avantageusement les "Outlines" pour gérer les plans des ordres SQL. Vous trouverez ci-dessous un exemple d'utilisation de cette nouvelle fonctionnalité.

Il faut noter que la gestion automatique d'Oracle SQL Plan Management fait parti du Tuning Pack mais commençons par un exemple d'utilisation avec Oracle 11g Enterprise Edition, sans pack. La documentation Oracle explique très bien cette fonctionnalité et je ne la paraphraserai pas ; vous ne devriez pas en avoir besoin pour lire ce qui suit (quoique !) :
Etape 1. Configuration et schéma exemple

Pour commencer, créez une table que vous allez remplir avec 10000 lignes.
create table gark
(id number not null);

begin
for i in 1..10000 loop
insert into gark(id)
values (i);
end loop;
commit;
end;
/
exec dbms_stats.gather_table_stats(user, 'GARK')
Etape 2. Requête et Plan

Avec SQL*Plus (ça ne marche pas avec SQL*Developer parce des ordres SQL sont générés entre les différentes étapes d'exécution d'un script), exécutez la requête suivante et visualisez son plan a l'aide du script ci-dessous :
set serveroutput off

select a.id,
b.id
from gark a,
gark b
where a.id=500
and b.id=a.id;

select * from table(dbms_xplan.display_cursor);
Le plan ressemble à celui ci :
Execution Plan
----------------------------------------------------------
Plan hash value:
2625395012
----------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 24 | 23 (5)|
|* 1 | HASH JOIN | | 4 | 24 | 23 (5)|
|* 2 | TABLE ACCESS FULL| GARK | 2 | 6 | 11 (0)|
|* 3 | TABLE ACCESS FULL| GARK | 2 | 6 | 11 (0)|
----------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("B"."ID"="A"."ID")
2 - filter("A"."ID"=500)
3 - filter("B"."ID"=500)
Etape 3. Enregistrer le plan dans les baselines de SQL Plan Management

Pour réaliser cette opération manuellement, vous devez relever le SQL_ID de la requête. Notez la PLAN_HASH_VALUE qui s'est affichée dans votre plan, puis recherchez le SQL_ID correspondant :
accept plan_hash_value prompt "Enter value for plan_hash_value: "

select distinct sql_id, plan_hash_value
from v$sql_plan
where plan_hash_value=&plan_hash_value;

SQL_ID PLAN_HASH_VALUE
------------- ---------------
4pznd20f4x8tf 2625395012

La requête suivante affiche les baselines enregistrées dans la base de données. Cette liste est normalement vide sauf si vous avez utiliser cette fonctionnalité ou positionne le paramètre optimizer_capture_sql_plan_baselines à true
col SQL_HANDLE format a24
col PLAN_NAME format a29
col PARSING_SCHEMA_NAME format a8
select SQL_HANDLE,
PLAN_NAME,
PARSING_SCHEMA_NAME,
ENABLED,
ACCEPTED,
SQL_TEXT
from dba_sql_plan_baselines;

no row selected.
Vous pouvez ensuite enregistrez votre plan dans les baselines. Le script qui suit :
  • Enregistre le plan dans SQL Plan Management
  • Affiche le plan enregistré
accept sql_id prompt "Enter value for sql_id: "
4pznd20f4x8tf

declare
gg binary_integer;
begin
gg:=dbms_spm.load_plans_from_cursor_cache(
sql_id=>'&sql_id');
end;
/

PL/SQL procedure successfully completed.

select SQL_HANDLE,
PLAN_NAME,
PARSING_SCHEMA_NAME,
ENABLED,
ACCEPTED,
SQL_TEXT
from dba_sql_plan_baselines;

SQL_HANDLE PLAN_NAME PARSING_ ENA ACC
------------------------ ----------------------------- -------- --- ---
SQL_TEXT
--------------------------------------------------------------------------------
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b SCOTT YES YES
select a.id,
b.id
from gark a,
gark b
where a.id=500
and b.id=a.id
Etape 4. Tester que la "baseline" empêche l'évolution du plan

Pour vérifier le fonctionnement, nous allons :
  • Créer un index qui pourrait améliorer le plan
  • Vérifiez le contenu du baseline après la création de l'index
  • Vider la shared pool et s'assure que le plan n'est plus dans la SGA
  • Exécutez la requête et afficher son plan
1- Créer un index
create index gark_idx
on gark(id);

Index created.
2- Si après avec créé l'index, vous regardez ce qui est stocké dans le baseline. C'est le plan d'avant la création de l'index :
accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa

select * from table(
dbms_xplan.display_sql_plan_baseline(
sql_handle=>'&sql_handle'));

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------
SQL handle: SYS_SQL_9ff3489361e958aa
SQL text: select a.id, b.id from gark a, gark b where a.id=
and b.id=a.id
---------------------------------------------------------------------------

---------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_61e958aa45bb399b
Enabled: YES Fixed: NO Accepted: YES Origin: MANUAL-LOAD
---------------------------------------------------------------------------

Plan hash value: 2625395012

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 24 | 23 (5)| 00:00:01 |
|* 1 | HASH JOIN | | 4 | 24 | 23 (5)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| GARK | 2 | 6 | 11 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| GARK | 2 | 6 | 11 (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("B"."ID"="A"."ID")
2 - filter("A"."ID"=500)
3 - filter("B"."ID"=500)
ERROR: User has no SELECT privileges on objects of the SQL plan baseline
3- Ensuite vous videz la shared pool (Pour être sur...) et vous vérifiez que le SQL_ID n'y est plus :
alter system flush shared_pool;

System altered.

accept sql_id prompt "Enter value for sql_id: "
4pznd20f4x8tf

select sql_id,
plan_hash_value
from v$sql
where sql_id='&sql_id';

no rows selected
4- Enfin vous exécutez la requête encore et visualisez son plan ne change pas malgré l'index :
set serveroutput off

select a.id,
b.id
from gark a,
gark b
where a.id=500
and b.id=a.id;

select * from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
SQL_ID 06uwqz3vkq97v, child number 1
-------------------------------------
select a.id, b.id from gark a, gark b where a.id=500
and b.id=a.id

Plan hash value: 2625395012

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 23 (100)| |
|* 1 | HASH JOIN | | 4 | 24 | 23 (5)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| GARK | 2 | 6 | 11 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| GARK | 2 | 6 | 11 (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("B"."ID"="A"."ID")
2 - filter("A"."ID"=500)
3 - filter("B"."ID"=500)

Note
-----
- SQL plan baseline SYS_SQL_PLAN_61e958aa45bb399b used for this statement
Il est intéressant de noter qu'un autre plan est ajouté automatiquenent dans le baselines pour lequel ACCEPTED='NO' et ORIGIN='AUTO-CAPTURE'. Ce plan n'est pas utilisé par la requête :
select SQL_HANDLE,
PLAN_NAME,
PARSING_SCHEMA_NAME,
ENABLED,
ACCEPTED,
SQL_TEXT
from dba_sql_plan_baselines;

SQL_HANDLE PLAN_NAME PARSING_ ENA ACC
------------------------ ----------------------------- -------- --- ---
SQL_TEXT
-----------------------------------------------------------------------
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b SCOTT YES YES
select a.id,
b.id
from gark a,
gark b
where a.id=500
and b.id=a.id

SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa SCOTT YES NO
select a.id,
b.id
from gark a,
gark b
where a.id=500
and b.id=a.id

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa

select * from table(
dbms_xplan.display_sql_plan_baseline(
sql_handle=>'&sql_handle'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL handle: SYS_SQL_9ff3489361e958aa
SQL text: select a.id, b.id from gark a, gark b where a.id=500 and
b.id=a.id
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_61e958aa45bb399b
Enabled: YES Fixed: NO Accepted: YES Origin: MANUAL-LOAD
--------------------------------------------------------------------------------
Plan hash value: 2625395012

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 15 (7)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 6 | 15 (7)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| GARK | 1 | 3 | 7 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| GARK | 1 | 3 | 7 (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("B"."ID"="A"."ID")
2 - filter("A"."ID"=500)
3 - filter("B"."ID"=500)

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_61e958aafcd784aa
Enabled: YES Fixed: NO Accepted: NO Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------
Plan hash value: 886284999
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (34)| 00:00:01 |
| 1 | MERGE JOIN CARTESIAN| | 1 | 6 | 3 (34)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | GARK_IDX | 1 | 3 | 1 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 1 | 3 | 2 (50)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | GARK_IDX | 1 | 3 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("A"."ID"=500)
4 - access("B"."ID"=500)

Etape 5. Faire évoluer le plan

La fonction evolve_sql_plan_baseline permet de valider les nouveaux plans dans la baseline et ainsi de faire évoluer automatiquement les plans stockés si ceux-ci sont plus performants :
accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa

var v_out clob
exec :v_out:=dbms_spm.evolve_sql_plan_baseline(-
sql_handle=>'&sql_handle')
print v_out

select SQL_HANDLE,
PLAN_NAME,
PARSING_SCHEMA_NAME,
ENABLED,
ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE PLAN_NAME PARSING_ ENA ACC
------------------------ ----------------------------- -------- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b SCOTT YES YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa SCOTT YES YES
Vous constatez que les 2 plans sont désormais acceptés et si vous exécutez de nouveau la requête, le nouveau plan (avec l'index) est utilisé :
select a.id,
b.id
from gark a,
gark b
where a.id=500
and b.id=a.id;

select * from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
----------------------
SQL_ID 4pznd20f4x8tf, child number 0
-------------------------------------
select a.id, b.id from gark a, gark b where a.id=500 and
b.id=a.id

Plan hash value: 886284999
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | MERGE JOIN CARTESIAN| | 1 | 6 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | GARK_IDX | 1 | 3 | 1 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 1 | 3 | 1 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | GARK_IDX | 1 | 3 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("A"."ID"=500)
4 - access("B"."ID"=500)

Note
-----
- SQL plan baseline SYS_SQL_PLAN_61e958aafcd784aa used for this statement
Etape 6. Revenir en arrière

Il est bien sur possible d'enlever le plan des baselines pour fixer un plan (cf point 7). Toutefois l'attribut FIXED des baselines permet de forcer les plan qui doivent être utilisés parmi ceux acceptés. Vous pouvez donc en garder tous les plans dans les baselines pour revenir simplement en arrière :
accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa
accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aa45bb399b

declare
v_out binary_integer;
begin
v_out := dbms_spm.ALTER_SQL_PLAN_BASELINE(
sql_handle =>'&sql_handle',
plan_name=>'&plan_name',
attribute_name=>'fixed',
attribute_value=>'YES');
end;
/

select SQL_HANDLE,
PLAN_NAME,
FIXED,
ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE PLAN_NAME FIX ACC
------------------------ ----------------------------- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b YES YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa NO YES

select a.id,
b.id
from gark a,
gark b
where a.id=500
and b.id=a.id;

select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-----------------
SQL_ID 4pznd20f4x8tf, child number 2
-------------------------------------
select a.id, b.id from gark a, gark b where a.id=500 and
b.id=a.id

Plan hash value: 2625395012

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 15 (100)| |
|* 1 | HASH JOIN | | 1 | 6 | 15 (7)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| GARK | 1 | 3 | 7 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| GARK | 1 | 3 | 7 (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("B"."ID"="A"."ID")
2 - filter("A"."ID"=500)
3 - filter("B"."ID"=500)
Pour revenir à l'état précédent, il suffit de passer l'attribut FIXED à NO :
accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa
accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aa45bb399b

declare
v_out binary_integer;
begin
v_out := dbms_spm.ALTER_SQL_PLAN_BASELINE(
sql_handle =>'&sql_handle',
plan_name=>'&amp;amp;plan_name',
attribute_name=>'fixed',
attribute_value=>'NO');
end;
/

select SQL_HANDLE,
PLAN_NAME,
FIXED,
ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE PLAN_NAME FIX ACC
------------------------ ----------------------------- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b NO YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa NO YES
Etape 7. Supprimer les plans de la baseline

Pour supprimer les plans enregistrés dans les baselines, il suffit d'utiliser la fonction drop_sql_plan_baseline du package dbms_spm comme ci-dessous :
col schema format a5
col SQL_HANDLE format a24
select SQL_HANDLE,
PLAN_NAME,
PARSING_SCHEMA_NAME SCHEMA,
ENABLED,
ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE PLAN_NAME SCHEM ENA ACC
------------------------ ----------------------------- ----- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b SCOTT YES YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa SCOTT YES YES

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa
accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aa45bb399b

DECLARE
gg binary_integer;
BEGIN
gg:=DBMS_SPM.DROP_SQL_PLAN_BASELINE(
SQL_HANDLE => '&sql_handle',
PLAN_NAME => '&amp;amp;plan_name');
END;
/
accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aafcd784aa
DECLARE
gg binary_integer;
BEGIN
gg:=DBMS_SPM.DROP_SQL_PLAN_BASELINE(
SQL_HANDLE => '&sql_handle',
PLAN_NAME => '&amp;amp;plan_name');
END;
/

col schema format a5
col SQL_HANDLE format a24
select SQL_HANDLE,
PLAN_NAME,
PARSING_SCHEMA_NAME SCHEMA,
ENABLED,
ACCEPTED
from dba_sql_plan_baselines;

no rows selected
Etape 8. SQL Plan Management et Tuning Pack 11g

Le paramètres optimizer_capture_sql_plan_baselines permet de capturer automatiquement les plans utilisés tandis que le SQL Tuning Advisor programmé toutes les nuits par défaut en 11g fait évoluer les plans tout en considérant bien plus que les statistiques. : les vrais performances ? 11g avec les Packs Diagnostic et Tuning pourraient bien nous faire entrer dans une nouvelle ère ; reste à valider à quel point les advisors évoluent eux aussi !
alter system set optimizer_capture_sql_plan_baselines=true
Etape 9. Supprimer le données exemples :

Pour supprimer les structures créées précédemment, utilisez le script ci-dessous :
drop index gark_idx;
drop table gark;
Etape 10. Conclure

Utilisé en mode manuel, SQL Plan Management simplifie grandement la manière de figer un plan par rapport aux Outlines. Enfin, j'espère bien l'utiliser en mode automatique sur un vrai environnement d'ici peu, histoire de vraiment réaliser, autrement qu'en théorie, les gains auxquels on peut s'attendre

Alors, qu'est-ce que vous en pensez ?

13 août 2007

Quelques liens intéressants à propos d'Ubuntu et de sécurité

Ces dernières semaines, j'ai eu l'occasion de "migrer" mon laptop d'Oracle Enterprise Linux à Ubuntu, chaudement recommandé par mon nouvel ami brésilien. Bien sur j'utilise plus que jamais Oracle EL pour vos serveurs alors que celui-ci continue à gagner momentum.

Bref, ça a été l'occasion de rencontrer de nouveaux problèmes, jusqu'alors inconnus ou auxquels je n'avais pas trouvé de solutions ou simplement parce que je ne m'étais pas encore posé la question.
  • Truecrypt permet de créer un système de fichier chiffré (et même caché) et ainsi protéger vos données sensible du vol de votre Laptop par exemple. La page "man" associée est "self-explicite"
Pour monter votre filesystem :
sudo -s
truecrypt -N 1 /dev/sda5
mount /dev/mapper/truecrypt1 /media/me
Pour le démonter :
sudo -s
umount /media/me
truecrypt -d /dev/sda5
  • Dans la même veine, ce Post décrit comment rendre "anonyme" vos passages sur le web avec Tor et FoxyProxy
  • Pour se protéger contre les Cross Site Scripts (xss), NoScript est une extension pour Firefox qui permet de supprimer toutes vos traces collectées par des JavaScripts.
  • AdsBlock Plus est enfin un outil pour supprimer les bandeaux de publicité à peu prêt PARTOUT !
Autres informations à propos d'Ubuntu livrées en vrac :
xmodmap -e “clear Lock”

Autres sujets d'intérêt, même si ça peut aussi paraître un peu éloigné de ce qui précède :

Sécurité orientée données : JavaEE et Oracle

L'article que j'aurais rêvé d'écrire explique comment tirer parti de la sécurité associée aux données pour votre application JavaEE. Pensez-y un peu :
  • Pourquoi, quand vous développez une application, s'interdire de ramener telle ou telle données pour telle catégorie d'utilisateur ?
  • Pourquoi s'assurer que vous ne laissez pas l'opportunité à une injection SQL ?
  • Pourquoi votre application gère les accès avec attention mais si vous accéder à la base données vous êtes DBA ?
J'avais commencé, il y a un an maintenant, à construire une solution qui permet de lier les utilisateurs de votre LDAP (iPlanet) avec une base de données. La solution en place, l'article ci-dessus explique comment continuer en intégrant la sécurité définie au niveau des données dans votre application JavaEE. D'autre part, Oracle permet de controler les accès par lignes (cf cet autre post) et/ou colonnes grâce à VPD. "Label Security" est

Reste une des limites que je citais : en 10g, ça ne marche qu'avec les driver JDBC Type 2 (i.e. JDBC/OCI). Une bonne nouvelle n'allant jamais seule, il est probable qu'11g lève cette restriction (Au moins d'après la doc). Reste donc à tout recommencer avec 11g pour montrer qu'une solution où la sécurité est mise en oeuvre sur tous les composants de l'infrastructure est maintenant mature. Je le note pour un prochain Post !

Anciennes fonctionnalités d'Oracle 11g...

On parle toujours des nouvelles fonctionnalités d'Oracle 11g. Pourtant 11g apporte aussi son lot d'"anciennes" fonctionnalités. Il faut dire qu'en général, on en souhaite la fin :
  • iSQL*Plus disparaît donc enfin ; il faut dire que cette initiative apparue un peu avant 10g avait été très malheureuse : merci donc !
  • Oracle Workflow risque de laisser quelques utilisateurs du NCA sur le carreau. Une autre époque sans doute ?
  • Oracle Data Mining Scoring Engine est mort ! C'était quoi ?
  • Oracle Enterprise Manager Java Console est ma plus grande perte et de loin ! Elle m'a encore sauvée la mise il y a quelques semaines, en 10g ! Reste à espérer que :
    • SQL*Developer (ou JDeveloper) arrive bientôt à gérer les classes Java dans la base de données aussi bien que cette Console
    • Qu'un outil "utilisable" reprenne la gestion de LogMiner ou des Enterprise Users.
Au revoir donc anciennes fonctionnalités d'Oracle 11g. Vous ne me manquerez sans doute pas trop !

Fonctions "Built-In" d'Oracle

Nouveauté d'Oracle 11g, les vues V$SQLFN_METADATA et V$SQLFN_ARG_METADATA vous permettent de lister les fonctions Built-In d'Oracle ainsi que leurs paramètres. A la description de ces vues, vous les utiliserez dans une application qui génère du SQL automatiquement comme un requêteur par exemple. Parmi les colonnes intéressantes : la colonne VERSION de V$SQLFN_METADATA !
select distinct NAME
from V$SQLFN_METADATA
where name is not null
order by name;

NAME
------------------------------
!=
<
<=
=
...
XMLTRANSFORMBLOB
XS_SYS_CONTEXT

457 rows selected.

12 août 2007

Real Time SQL monitoring /*+ 11g Tuning Pack */

"Real Time SQL Monitoring" est activée par défaut si Oracle Tuning Pack 11g est configuré. Quand un ordre s'exécute en parallèle ou si une requête utilise plus de 5 secondes de CPU ou d'I/O, vous pouvez visualiser les informations associées à ce nouvel outil. Comme souvent dans ce Blog, je ne parlerai pas de ce que vous pouvez faire avec Enterprise Manager mais plutôt avec SQL*Plus. Ainsi, alors que V$SESSION_LONGOPS permet de suivre des opérations longues y compris une étape d'un plan, V$SQL_PLAN_MONITOR permet de suivre l'évolution de l'ensemble d'un plan. L'exemple qui suit illustre cette nouvelle capacité mais avant tout, reportez-vous à la documentation associée regroupée ci-après :
Etape 1 : Configuration, Schéma exemple et SQL

Vérifiez que les packs Diagnostic et Tuning sont activés. Attention vous devez avoir une licence :
SQL> show parameter control_management_pack_access

NAME VALUE
------------------------------ -----------------
control_management_pack_access DIAGNOSTIC+TUNING
Créez un schéma exemple
create table gark(a number,
b number);

begin
for i in 1..100000 loop
insert into gark values (mod(i,19), mod(i,17));
commit;
end loop;
end;
/
Pour utiliser "Real Time SQL Monitoring", nous allons lancer une requête longue. Voici l'exemple que je vous propose d'utiliser celle qui suit :
select /*+ use_nl(a b) */ count(*)
from gark a, gark b
where a.a=b.b;
Avant de l'exécutez, préparez le terrain pour superviser la requête; Dans un vrai cas, vous repérerez la requête au moyen d'un "Top SQL" ou d'un "Top Sessions". Pour cet exemple nous allons repérer la session avant de commencer. Par exemple si on execute notre exemple sous le schéma SCOTT, connectez-vous d'abord DBA et donnez le privilège "SELECT on SYS.V_$SESSION" à SCOTT
sqlplus / as sysdba
grant select on sys.v_$session to scott;
Enfin repérez la session à partir de laquelle vous allez exécuter la requête :
select sid, serial#, audsid
from v$session
where audsid=sys_context('USERENV','SESSIONID');

SID SERIAL# AUDSID
---------- ---------- ----------
130 99 200038

select /*+ use_nl(a b) */ count(*)
from gark a, gark b
where a.a=b.b;
Etape 2 : Suivre l'évolution de votre requête

Une fois l'ordre lancé, V$SQL_MONITOR conservera ses statistiques pendant son exécution et sans doute quelques minutes après sauf si vous avez vraiment beaucoup d'activité. Bien sur, ceci n'est vrai que parce que l'ordre est très long (merci le HINT). Contrairement à V$SQL, les statistiques ne sont pas cumultives et vous aurez une entrée par exécution. V$SQL_PLAN_MONITOR stocke un niveau de détail encore supérieur puisqu'elle conserve des statistiques pour chacune des étapes du plan de la requête. Repérez d'abord l'ordre SQL qui vous intéresse (Il faut plusieurs secondes pour qu'il apparaisse) :
accept v_sid prompt "Enter Session ID : "
130
accept v_serial prompt "Enter Session Serial# : "
200038
col key format 999999999999
set colsep '|'
select key, sql_id, sql_exec_id,
to_char(max(sql_exec_start) ,'DD/MM/YYYY HH24:Mi:SS') sql_exec_start,
sql_child_address child_address
from v$sql_monitor
where sid=&v_sid
and session_serial#=&v_serial
group by key, sql_id, sql_exec_id, sql_child_address
order by sql_exec_start;

KEY|SQL_ID |SQL_EXEC_ID|SQL_EXEC_START |CHILD_AD
------------|-------------|-----------|-------------------|--------
304942678017|4z9f29d23f72c| 16777216|12/08/2007 03:58:21|305C173C
519691042820|4z9f29d23f72c| 16777217|12/08/2007 06:00:43|331B81F8
408021893126|4z9f29d23f72c| 16777218|12/08/2007 08:05:42|30615618
Une fois que vous avez repéré l'ordre qui vous intéresse (SQL_ID, SQL_EXEC_ID et SQL_EXEC_START forment une clé de pour indetifier cet ordre), vous pouvez suivre l'evolution de l'exécution de la requête :
accept v_sql_id prompt "Enter the SQL_ID to look at : "
4z9f29d23f72c
accept v_sql_exec_id prompt "Enter the associated SQL_EXEC_ID to look at : "
16777218
accept v_sql_exec_start prompt "Enter the Execution Start Date (DD/MM/YYYY HH24:MI:SS) : "
12/08/2007 08:05:42
col id format 999
col operation format a25
col object format a6
set colsep '|'
set lines 100
select p.id,
rpad(' ',p.depth*2, ' ')||p.operation operation,
p.object_name object,
p.cardinality card,
p.cost cost,
substr(m.status,1,4) status,
m.output_rows
from v$sql_plan p, v$sql_plan_monitor m
where p.sql_id=m.sql_id
and p.child_address=m.sql_child_address
and p.plan_hash_value=m.sql_plan_hash_value
and p.id=m.plan_line_id
and m.sql_id='&&v_sql_id'
and m.sql_exec_id=&&v_sql_exec_id
and m.sql_exec_start=to_date('&&v_sql_exec_start', 'DD/MM/YYYY HH24:MI:SS')
order by p.id;

ID|OPERATION |OBJECT| CARD| COST|STAT|OUTPUT_ROWS
--|-------------------------|------|----------|----------|----|-----------
0|SELECT STATEMENT | | | 6718856|EXEC| 0
1| SORT | | 1| |EXEC| 0
2| NESTED LOOPS | | 543129616| 6718856|EXEC| 19857217
3| TABLE ACCESS |GARK | 100000| 69|EXEC| 3772
4| TABLE ACCESS |GARK | 5431| 67|EXEC| 19857217
Vous pouvez simplement réexécuter l'ordre SQL pour suivre l'évolution de votre requête :
/
ID|OPERATION |OBJECT| CARD| COST|STAT|OUTPUT_ROWS
--|-------------------------|------|----------|----------|----|-----------
0|SELECT STATEMENT | | | 6718856|EXEC| 0
1| SORT | | 1| |EXEC| 0
2| NESTED LOOPS | | 543129616| 6718856|EXEC| 167404623
3| TABLE ACCESS |GARK | 100000| 69|EXEC| 31806
4| TABLE ACCESS |GARK | 5431| 67|EXEC| 167404623

/
ID|OPERATION |OBJECT| CARD| COST|STAT|OUTPUT_ROWS
--|-------------------------|------|----------|----------|----|-----------
0|SELECT STATEMENT | | | 6718856|DONE| 1
1| SORT | | 1| |DONE| 1
2| NESTED LOOPS | | 543129616| 6718856|DONE| 526317649
3| TABLE ACCESS |GARK | 100000| 69|DONE| 100000
4| TABLE ACCESS |GARK | 5431| 67|DONE| 526317649
Etape 3 : plus d'informations

A noter que si vous voulez forcer l'affichage des statistiques d'une requête dans les vues V$SQL_MONITOR et V$SQL_PLAN_MONITOR, vous pouvez utiliser le HINT /*+ MONITOR */. Le HINT /*+ NO_MONITOR */ empêche au contraire cet affichage. D'autre part, avec cette requête et mon paramètrage les colonnes WORKAREA_MEM, WORKAREA_MAX_MEM, WORKAREA_TEMPSEG et WORKAREA_MAX_TEMPSEG de V$SQL_PLAN_MONITOR ne s'affiche pas. J'imagine que c'est lié, la fonction DBMS_SQLTUNE.REPORT_SQL_MONITOR n'affiche rien non plus. Dommage la documentation est très prometteuse :
variable v_report CLOB;
set long 10000000
set longchunksize 10000000
set linesize 200
BEGIN
:v_report :=DBMS_SQLTUNE.REPORT_SQL_MONITOR(
sql_id=>'&&v_sql_id',
sql_exec_id=>&&v_sql_exec_id,
sql_exec_start=>to_date('&&v_sql_exec_start', 'DD/MM/YYYY HH24:Mi:SS'),
report_level=>'ALL');
END;
/
print :v_report
Si vous avez une idée pour le faire fonctionner, je suis preneur ? J'hésite entre une limitation de la Developer Release ou du DBA (moi)

Etape 4 : Quand vous avez fini
drop table gark purge;

10 août 2007

Quand allez-vous mettre Oracle 11g en production ?

Votre doc 11g favorite...

Oracle Database Licensing Information - 11g Release 1 ; Vous découvrirez notamment 4 nouvelles options :
  • Oracle Active Data Guard
  • Oracle Advanced Compression
  • Oracle Real Application Testing
  • Oracle Total Recall
Dommage que les prix ne soient toujours pas à jour

RMAN et Data Guard 11g (Part 5) : Snapshot Standby Database

Peut-être ma fonctionnalité préférée ! En 11g, la Standby peut-être utilisée pour faire des tests tout en garantissant la sécurité de sa base de données primaire. Cette fonctionnalités appelées Snapshot Standby Database vous permet d'utiliser votre Standby comme une pré-production et de résoudre 2 problèmes simultanément qui sont :
  • Comment avoir une pre-production représentative de la production ?
  • Comment utiliser la pre-production comme système de secours ?
Le principe de fonctionnement de la "Snapshot Standby Database" est certes complètement attendue mais la simplicité de sa mise en œuvre n'en est pas moins étonnante. Dans son principe, voila comment ça marche :
  • Pour commencer :
    • Le système crée un "guaranteed restore point" au moment ou votre Standby devient Snapshot Stanby
  • Lorsque vous faites vos tests :
    • La Standby est ouverte en Read-Only et les flashback logs vous permettront de vous resynchroniser le moment voulu avec votre base de données primaire
    • Pendant vos tests, la base de données continue à recevoir les archivelogs de la base de données primaire. De cette manière en cas de crash de la primaire, vous disposerez de tous les éléments pour reconstituer la standby
    • (Vous travaillez sur une nouvelle incarnation de votre base de données puisque celle-ci sera ouverte avec un open resetlogs)
  • Pour finir :
    • Une simple instruction ramène votre standby à son point de restauration grâce à la Flashback database
    • Les logs transmis sont appliqués et la Standby resynchronisée avec sa base de données primaire
Nous supposerons que vous avez déjà une standby constituée avec le Broker. Vous allez vite comprendre à quel point c'est vraiment simple.

1- Activer Flashback Database sur la Standby

(Si ce n'est pas déja le cas)
alter system
set db_recovery_file_dest_size=2G
scope=both;

System altered.

alter system
set db_recovery_file_dest=
'/u01/app/oracle/flash_recovery_area'
scope=both;

System altered.

alter database mount;

Database altered.

alter database flashback on;

Database altered.


2- Convertir la Standby en Snapshot Standby

Sur la standby :
sqlplus / as sysdba
ALTER DATABASE CONVERT TO SNAPSHOT STANDBY

dgmgrl
connect /

DGMGRL> show configuration;

Configuration
Name: arkzoyd
Enabled: YES
Protection Mode: MaxPerformance
Databases:
ORCL - Primary database
GARK - Snapshot standby database

Fast-Start Failover: DISABLED

Current status for "arkzoyd":
SUCCESS

3- Tester votre application
  • Sur la standby
create table gark_snap(id number) tablespace users;

Table created.

SQL> insert into gark_snap values (2);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from gark_snap;

ID
----------
2
  • Sur la primaire
col name format a10
select NAME, SEQUENCE#, ARCHIVED, APPLIED
from v$archived_log
where dest_id=2
and sequence#>=30
order by sequence#;

NAME SEQUENCE# ARC APP
---------- ---------- --- ---
gark 30 YES YES
gark 31 YES NO
gark 32 YES NO
gark 33 YES NO

alter system switch logfile;

System altered.

select NAME, SEQUENCE#, ARCHIVED, APPLIED
from v$archived_log
where dest_id=2
and sequence#>=30
order by sequence#;

NAME SEQUENCE# ARC APP
---------- ---------- --- ---
gark 30 YES YES
gark 31 YES NO
gark 32 YES NO
gark 33 YES NO
gark 34 YES NO
4- Revenir em mode standby
shutdown immediate;
startup nomount;

alter database
convert to physical standby;

shutdown;

startup mount;

alter database
recover managed standby database
disconnect;

Sur la primaire après un petit moment, ou après avoir change de log si vous voulez accélérer le processus, vous constaterez que les archives sont de nouveau automatiquement appliquées :
set pages 1000
col name format a10
select NAME, SEQUENCE#, ARCHIVED, APPLIED
from v$archived_log
where dest_id=2
and sequence#>=30
order by sequence#;

NAME SEQUENCE# ARC APP
---------- ---------- --- ---
gark 30 YES YES
gark 31 YES YES
gark 32 YES YES
gark 33 YES YES
gark 34 YES YES
gark 35 YES YES
gark 36 YES YES
gark 37 YES NO
Et voilà ! Comme si nous n'avions jamais fait de tests et les archives ont toujours été transmises à la standby.

-Grégory!

RMAN et Data Guard 11g (Part 4) : Select et Standby Physique

En 11g, vous pouvez exécuter des ordres SELECT alors que la base de données standby est en train de d'appliquer les fichiers logs de la base de données primaire. Une fois votre standby créée, il suffit de vous connecter et d'opérer quelques ordres très simples :

1- Configurer votre base de donnees standby pour qu'elle puisse etre ouverte en read-only

Il est possible que vous ayez a modifier plusieurs chose sur la standby pour pouvoir l'ouvrir en read-only. Par exemple, audit_trail ne doit pas être égal à DB :
alter system set audit_trail=xml scope=spfile;
Il faut que vous ayez un fichier temporaire valide pour vos tablespaces temporaires sur la standby :
shutdown immediate;

startup mount;

alter database recover managed standby database cancel;

alter database
tempfile '/u01/app/oracle/oradata/ORCL/temp01.dbf'
drop;

alter tablespace TEMP
add tempfile '/u01/app/oracle/oradata/GARK/temp01.dbf'
size 10M;

2- Démarrer la Standby en Read-Only et démarrer le processus d'apply

Simplement les 2 commandes qui suivent :
alter database open read only;

alter database recover managed standby database disconnect;
Remarque :
Le processus d'apply ne doit pas être démarrer pour passer la base de données en Read-Only. Si ce n'est pas votre cas, utilisez la commande qui suit sur la standby avant de passer la standby en read-only :
alter database recover managed standby database cancel;
3- Tester le fonctionnement de votre standby :

Voici un test simple pour visualiser le fonctionnement de cette nouvelle possibilité des Standby physiques :

3.a- sur la Standby :
SQL> select * from gark;
select * from gark
*
ERROR at line 1:
ORA-00942: table or view does not exist

3.b- sur la primaire :
SQL> create table gark(id number)
2 tablespace users;

Table created.

SQL> insert into gark values (1);

1 row created.

SQL> commit;

Commit complete.

SQL> alter database switch logfile;
Remarque :
Si votre base de donnees fonctionne en Real-Time Apply, le switch logfile est inutile.

3.c- Sur la Standby :
SQL> select * from gark;

ID
----------
1

-Grégory!