Meetup avec Oren Nakdimon (db oriented)

MeetupOren Nakdimon, créateur du blog (http://db-oriented.com/) et ORACLE Ace Associate viendra présenter deux sujets au prochain Paris-Province Oracle Meetup:

  • Nouveautés de la 12c qui augmentent votre productivité
  • Utilisation de la fonctionnalité Edition-Based Redefinition.

Le groupe Paris Oracle Meetup, est animé entr’autres par Yves Colin et Olivier Bernhard tous trois très impliqués dans l’organisation des évènements du groupe.
Une bonne occasion de rejoindre le groupe Paris Oracle Meetup, de participer et de rencontrer d’autres passionnés des applications Oracle.

Dernier détail, et non des moindres, c’est à Nanterre, à 19h00 le lundi 3 Octobre 2016, mais surtout ces meetups sont tous GRATUITS ce qui de nos jours est de plus en plus rare. Profitez !

Sauvegarder sur ZDLRA via RMan en mode console

ZDLRA

ZDLRA, l’appliance oracle de “backup and recovery”

ZDLRA

fonctionne comme un lecteur de bande bien que thechniquement il s’agisse d’une base de données sur un Exadata. L’appliance consolide des sauvegardes incrémentales en son sein de manière à ce que la restauration full soit rapide, donc mis à part la première sauvegarde, toutes sont des sauvegardes différentielles (i.e. incremental in english). Pour effectuer unse sauvagarde on procèdera avec RMan comme pour tout autre sauvegarde différentielle sur bande. Il faut donc configurer les channels pour qu’ils utilisent bien la librairie ZDLRA et lui renseigner la manière de se connecter, à savoir via des informations de connexion pré-enregistrées dans un WALLET.

CONFIGURE CHANNEL DEVICE TYPE 'SBT_TAPE' FORMAT '%d_%U' PARMS "SBT_LIBRARY=[ORACLE_HOME]/lib/libra.so, ENV=(RA_WALLET='location=file:[ORACLE_HOME]/dbs/zdlra credential_alias=[ZDLAR-SCAN]:[ZDLRA-PORT]/[ZDLRA-SERVICE]:dedicated')";

Au delà de la configuration des channels, ZDLRA utilise un catalogue qu’elle héberge et dont l’alias est le même que celui utilisé pour configurer les channels :

[ZDLAR-SCAN]:[ZDLRA-PORT]/[ZDLRA-SERVICE]:dedicated

L’instruction classique de sauvegarde pourra par exemple être :

run
{
backup incremental level 1 cumulative device type sbt database;
}
exit;

Et c’est tout, avec ces simples instructions, il est possibles d’effectuer directement la sauvegarde des données de vos bases. C’est à dire, sans avoir à passer par OEM ou par une quelconque autre interface graphique. Donc de travailler directement en mode console. Bien sûr celà n’empêche pas la configuration dans OEM, mais je me penche sur le sujet.

Histogrammes en 11gR2

Les performances d’Oracle reposent sur l’analyse des requêtes SQL au travers du Cost Based Optimizer (CBO) qui s’appuie sur les statistiques sur les divers objets de la base. Toute le problématique des statistiques Oracle est d’avoir une représentation la plus fidèle possible des données en base en un minimum d’informations. La solution proposée par Oracle pour le faire est d’utiliser les histogrammes.

Au niveau des tables et sans s’occuper des problématiques de partitionnement les statistiques sont visibles dans des vues diverses qui vont donner des informations au niveau de la table (par exemple DBA_TABLES, ALL_TAB_STATISTICS, USER_TABLES …) puis pour les colonnes des tables (USER_TAB_COLUMNS, DBA_TAB_COL_STATISTICS …) et enfin pour certaines colonnes un histogramme est créé et visible dans des vues telles que DBA_COL_HISTOGRAMS. Reste à juger de leur pertinence afin de décider si oui ou non on en a besoin pour telle ou telle colonne.

Frequency Histograms

Un petit jeu de test ?

create table t0 ( c1 number );

insert into t0
select abs(ceil(dbms_random.normal*3))
from sys.dual
connect by rownum <= 1E5 ;

exec dbms_stats.gather_table_stats(user, 'T0', method_opt=>'FOR COLUMNS C1 SIZE 254', estimate_percent=>100)

On effectue un Explain plan pour toutes les requête dont le prédicat est C1=x où x varie de -2 à 15 que l’on compare au nombre effectif d’occurrence de chaque valeur ( count(*) group by C1 ).

Résultat

 

histo_freq

 

En bleu l’histogramme, en rouge le nombre effectif d’occurrences des valeurs données en abscisse … On a ici un histogramme qui représente parfaitement le contenu de la colonne C1, et qui contredit la documentation officielle du fait qu’il ne décroît pas progressivement au delà des valeurs minimales et maximales.

Pourquoi ?

L’histogramme a été calculé sur un échantillon de 100 %. Le CBO le considère comme “juste” en l’occurrence à raison, mais dans deux semaines ? Au bout de deux semaines aussi, et c’est nettement moins agréable.

Si on laisse Oracle choisir l’échantillon, ça donne quoi ?

exec dbms_stats.gather_table_stats(user, 'T0')

freq_auto

ATTENTION: L’échelle est logarithmique. Mais là on a bien ce que décrit la documentation. Et l’histogramme garde sa pertinence.

Height Balanced Histograms

En résumé: Les Frequency histograms c’est génial mais ça prend de la place donc si le nombre de valeur distinctes d’une colonne est trop important il faut trouver une autre solution pour rendre compte de la répartition non uniforme des données et ne pas trop encombrer le dictionnaire des données; c’est là qu’intervient le second type d’histogramme appelé height balanced histogram.

Oracle a décidé que le nombre maximal de valeurs distinctes qu’il allait stocker pour une colonne était de 254, au delà ou si on demande de calculer l’histogramme pour moins de valeurs qu’il n’en existe pour la colonne (non documenté) l’histogramme devient height balanced.

Un nouveau petit jeu de test ?

create table t1 ( c1 number );

insert /*+ APPEND */ into t1
select ceil(dbms_random.normal*35)
from sys.dual
connect by rownum 'FOR COLUMNS C1 SIZE 254', estimate_percent=>100)

create table t2 ( c1 number );

insert /*+ APPEND */ into t2
select (150-abs(c))*sign(c+0.1) 
from (select ceil(dbms_random.normal*35) c
      from sys.dual
      connect by rownum <=1E5 ) ;

On obtient les répartitions de données suivantes :

tables_loiNormale

Cardinalités sans histogramme

Si on calcule les statisitques sur la table en ne calculant pas l’histogramme on obtient la distribution de cardinalités suivantes (en vert):

no_histogram

Comme on pouvait s’y attendre, Oracle considère que chaque valeur est représentée autant que les autres. A noter tout de même: avant la valeur minimale et au delà de la valeur maximale détectée, le CBO estime une cardinalité progrssivement décroissante de la valeur moyenne calculée jusuq’à une valeur de 1. La raison en est que les statistiques n’ont pas vocation à être calculées en permanence et que donc, la table vivant, de nouvelles valeurs peuvent apparaitre.

Histogramme calculé sur un échantillon de 100% et pour 254 buckets

0n obtient la distribution de cardinalités suivantes (en bleu):

normal_h254_100

On constate :

  • Que l’histogramme fonctionne par palliers
  • Que dans chaque pallier chaque valeur n’est pas équitabement représentée.
  • Que l’estimation d’une cardinalité est au pire égale à l’estimation sans histogramme.

Histogramme calculé sur un échantillon de 100% et on laisse à Oracle le choix du nombre de buckets

normal_auto_e100

On constate :

  • Peu de différence avec la distribution précédente

Histogramme calculé sur un échantillon et pour un nombre du buckets nombre de buckets dont les choix sont laissés à Oracle

normal_auto_auto

On constate :

  • Cette fois ci, trois palliers
  • Une moins bonne correspodance des cardinalités estimées avec les cardinalités réelles

Globalement sur ces tests

Les histogrammes sont assez représentatifs des données (ouf !) mais peut-être que ça vient de la forme de la courbe, d’où l’idée d’essayer avec une courbe ‘inversée’.

On recrée le deuxième type d’histogramme (100% buckets auto)

normal_bol_h255_e100

On constate :

  • Une estimation aussi “bonne” ou “mauvaise” que l’estimation faite dans les mêmes conditions sur l’autre répartion de données.

Comment un histogramme est-il calculé ?

les données de la colonne sont ordonnées et le résultat découpé en autant de buckets que vous ou Oracle en avez décidé. A partir de là à chaque point de rupture entre deux buckets, la valeur est enregistrée.A chaque bucket correspond donc le nombre de lignes valuées de la table divisé par le nombre du buckets. A une valeur de la colonne qui est relevée correspond une cardinalité estimée au nombre de fois où elle a été relevée multiplié par le nombre de lignes que contient un bucket

Est-il possible de faire mieux ?

Oui mais c’est très coûteux: en effet, si on fait du dynamic sampling on obtient ça:

dynamic_sampling

Seulement, du coup, le temps de parsing est très fortement allongé et les lectures disques augmentées

Limites des histogrammes

Le calcul des histogrammes parait un bon compromis, cependant il est des cas où il s’avère inutile. Le graphe ci-après représente l’histogramme et les valeurs calculées sur une table de 10 millions de lignes sur une colonne présentant près de 1600 valeurs distinctes.

v1600_h254_100

On constate :

  • L’estimation faite est équivalente à celle qui serait survenue en l’absence d’histogramme. Si le calcul de l’histogramme n’est pas néfaste, il reste contre-productif.

Jusqu’à maintenant les histogrammes étaient calculés sur des colonnes de type numérique. Si on calcule un histogramme sur une colonne de type varchar2 on peut rencontrer un soucis dans le cas où un grand nombre de valeurs distinctes ont leur 32 premiers caractères en commun.

create table t6 ( c1 varchar2(64) );

insert /*+ APPEND */ into t6
select 'Foo Bar Baz Foo Bar Baz Foo Bar Baz '||to_char(ceil(dbms_random.normal*35))
from sys.dual
connect by rownum 'FOR COLUMNS C1 SIZE 254', estimate_percent=>100)

varchar

On constate :

  • L’estimation faite est égale au nombre de lignes de la table et donc totalement hors de propos.

Conclusion

Les histogrammes représentent un moyen intéressant d’avoir une estimation de la cardinalité réelle d’une occurrence de valeur pour une colonne donnée. Cependant, si les valeurs sont trop nombreuses ou pour certains types de données trop centrées autour d’une même valeur les statistiques calculées seront au mieux inutiles et au pire complètement erronées.

PS

Cet article est paru précédemment sur le site de mon ancien employeur.

Explain plan, Bind variables et Histogrammes (2/2)

Résumé des épisodes précédents

Lors d’un précédent article j’ai montré que l’instruction Explain Plan donnait un résultat discuttable dès lors que la requête étudiée impliquait des bind variables. Une question reste en suspens: Pour quelle raison les cardinalités calculées par Explain plan sont-elles fausses ?

Je vois circuler sur le net une explication farfelue selon laquelle explain plan ne déclencherait pas de hard parse et qu’en conséquence il n’irait pas rechercher la valeur des bind variables, Explication plus ou moins mélangée avec une demi vérité selon laquelle explain plan considérerait toutes les bind variables comme étant de type varchar2 (vrais) et que de ce fait il serait incapable d’aller chercher la valeur correspondante dans la table (faux).

Avant toute chose, il est important de se référer à Tom Kyte qui en la matière est quasiment indiscuttable (après tout, tout le monde peut se tromper):

explain plan always does a hard parse, it evaluated the query plan “in the here and now, as of this moment in time

Il est donc plus que probable que l’explication selon laquelle explain plan ne fait pas de hard parse est fausse. Pour s’en convaincre, je ferai des traces grace à l’évènement 10053 qui ne produit une trace que lors de hard parses.

Les tests effectués dans ce billet le sont en 11gR2.

Des objets pour tester …

Avant toutes chose, on se crée une petite table de test, indexée afin de l’optimiseur puisse choisir ou non de prendre l’index et avec des histogrammes pour que les opérations de bind peeking aient un sens.

drop table t1;
create table t1 ( c1 number, c2 number );

insert /*+ APPEND */ into t1
select abs(ceil(dbms_random.normal*3)), rownum
from sys.dual
connect by rownum 'FOR COLUMNS C1 SIZE 254', estimate_percent=>100)

La commande suivante liste les cardinalités (pêchées dans l’histogramme sur la colonne) associées à chaque valeurs de la colonne C1 de la table de test T1.

select TABLE_NAME
     , COLUMN_NAME
     , ENDPOINT_NUMBER - nvl(lag(ENDPOINT_NUMBER, 1) over (partition by TABLE_NAME, COLUMN_NAME order by ENDPOINT_NUMBER) , 0 ) card
     , ENDPOINT_VALUE
from dba_tab_histograms
where table_name='T1'
  and owner=user ;

TABLE_NAME                     C_N       CARD ENDPOINT_VALUE
------------------------------ --- ---------- --------------
T1                             C1       13088              0
T1                             C1       24444              1
T1                             C1       21250              2
T1                             C1       16146              3
T1                             C1       11145              4
T1                             C1        6867              5
T1                             C1        3752              6
T1                             C1        1948              7
T1                             C1         865              8
T1                             C1         316              9
T1                             C1         121             10
T1                             C1          42             11
T1                             C1          12             12
T1                             C1           3             13
T1                             C1           1             14

On a donc une colonne dont les valeurs ne sont pas uniformément réparties et si l’optimiseur fait son travail correctement, pour un contrainte de type C1=13 il devrait choisir un accès par index et pour une cuntrainte de type C1=1 il devrait choisir un parcours complet de la table.

Jeux de tests

Test 1 : Exécution d’une requête SQL avec bind variable dont la valeur produira peu de lignes (C1=13)

La mémoire est vide de tout plan pour cette table nouvellement créée, on s’attend à ce que, par le biais de l’histogramme, l’optimiseur s’apperçoive que la valeur 13 est peu représentée pour la colonne C1 et choisisse un accès par index.

variable s number 
exec :s := 13

alter session set tracefile_identifier = 'T1_EXE_NUM' ;
alter session set events='10053 trace name context forever' ;

select * from t1 where c1 = :s ;

        C1         C2
---------- ----------
        13      61103
        13      79854
        13      95305

alter session set events='10053 trace name context off' ;
alter session set tracefile_identifier='' ;

Trois lignes de remontées sur 100 000, on espère qu’un index a été utilisé …

select * from table ( dbms_xplan.display_cursor() ) ;

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
SQL_ID  6v6vzbxcpb91s, child number 0
-------------------------------------
select * from t1 where c1 = :s

Plan hash value: 1198593617

---------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost | Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |       |       |     2|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1      |     3 |    24 |     2| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | I_T1_C1 |     3 |       |     1| 00:00:01 |
---------------------------------------------------------------------------------

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

   2 - access("C1"=:S)

Formidable, le plan choisi est le bon, ou du moins celui que l’on attend, le curseur est en mémoire, on continue.

Test 2 : Production du plan d’exécution par explain plan

On joue cette fois-ci un explain plan, le curseur n’a pas quitté la mémoire, la variable ‘s’ n’a pas changé de valeur on pourrait penser :

  • Que le curseur étant en mémoire, explain plan va se contenter d’effectuer un soft parse et s’appercevant qu’il a déjà un curseur, produire le plan correspondant.
  • Que l’instruction explain plan ignore les curseurs en mémoire, va donc utiliser ce qu’il a a disposition (bind variable et histogramme) pour produire le même plan que plus haut.
alter session set tracefile_identifier = 'T2_XPL_NUM' ;
alter session set events='10053 trace name context forever' ;

explain plan for select * from t1 where c1 = :s ;

Explained.

alter session set events='10053 trace name context off' ;
alter session set tracefile_identifier='' ;

On le sait, on l’a vu lors de mon précédent article sur le sujet, ni le plan en mémoire, ni le même déduit à partir de la valeur de la variable ne sera produit :

select * from table ( dbms_xplan.display() ) ;

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------
Plan hash value: 838529891

--------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost | Time     |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  7143 | 57144 |    35| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |  7143 | 57144 |    35| 00:00:01 |
--------------------------------------------------------------------

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

   1 - filter("C1"=TO_NUMBER(:S))

13 rows selected.

Au passage on vérifie que des hard parses ont étés faits pour chacun de nos tests :

ho ls -rt $BDUMP/TTEST*T*trc
TTEST_ora_20250650_T1_EXE_NUM.trc
TTEST_ora_20250650_T2_XPL_NUM.trc

Comme prévu une analyse complète de la requête a été faite dans les deux cas et le plan généré est différent du plan initial.

On constate aussi un traitement différent :

  • Exécution :
    • 2 – access(“C1″=:S)
  • Explain plan
    • 1 – filter(“C1″=TO_NUMBER(:S))

Que dit Tom Kyte quand il évoque le sujet ?

The last bit about explain plan […] is the fact that explain plan doesn’t see your bind datatype. It presumes all binds are varchar2’s regardless of how the developer is binding.

On pourrait penser du fait que l’explain plan considère les bind variables systématiquement comme un type varchar2, la conversion influe sur le plan d’exécution et sur les cardinalités, et on se fourvoierait :

Test 3 : Exécution la même requête SQL avec bind variable de type varchar2

alter system flush shared_pool ;
variable s2 varchar2(3) 

exec :s2 := '13'
   
alter session set tracefile_identifier = 'T3_EXE_VC2' ;
alter session set events='10053 trace name context forever' ;

select * from t1 where c1 = :s2 ;

        C1         C2
---------- ----------
        13      61103
        13      79854
        13      95305

alter session set events='10053 trace name context off' ;
alter session set tracefile_identifier='' ;

Si l’explication selon laquelle un type varchar2 égare le CBO est vraie, le plan utilisé devrait utiliser un parcours complet de la table, il prendrait un index dans le cas contraire.

select * from table ( dbms_xplan.display_cursor() ) ;

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
SQL_ID  bmawjntgyr2p7, child number 0
-------------------------------------
select * from t1 where c1 = :s2

Plan hash value: 1198593617

---------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost | Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |       |       |     2|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1      |     3 |    24 |     2| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | I_T1_C1 |     3 |       |     1| 00:00:01 |
---------------------------------------------------------------------------------

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

   2 - access("C1"=TO_NUMBER(:S2))

Lors de l’exécution avec une bind variable de type varchar2, les opérations de bind peeking ont bien été faites et le bon plan choisi, la supposition que l’absence de bind peeking dans l’explain plan provient du type de la bind variable est fausse. Alors pourquoi ?

On s’intéresse ici aux contenus des fichiers de trace générés et surtout à la section où la Bind Variable est lue.

  • Lors de l’exécution on a le paragraphe suivant :
  • *******************************************
    Peeked values of the binds in SQL statement
    *******************************************
    ----- Bind Info (kkscoacd) -----
     Bind#0
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=00 csi=00 siz=24 off=0
      kxsbbbfp=110950ff8  bln=22  avl=02  flg=05
      value=13
    
    Final query after transformations:******* UNPARSED QUERY IS *******
    SELECT "T1"."C1" "C1","T1"."C2" "C2" FROM "SYS"."T1" "T1" WHERE "T1"."C1"=:B1
  • Lors de l’explain plan on a le paragraphe suivant :
  • *******************************************
    Peeked values of the binds in SQL statement
    *******************************************
    ----- Bind Info (kkscoacd) -----
     Bind#0
      oacdty=01 mxl=32(00) mxlc=00 mal=00 scl=00 pre=00
      oacflg=00 fl2=1010000 frm=01 csi=873 siz=32 off=0
      kxsbbbfp=11097d100  bln=32  avl=00  flg=05
    
    Final query after transformations:******* UNPARSED QUERY IS *******
    SELECT "T1"."C1" "C1","T1"."C2" "C2" FROM "SYS"."T1" "T1" WHERE "T1"."C1"=TO_NUMBER(:B1)

Dans les deux cas, l’analyseur de requête constate qu’il a une bind variable, mais dans le second il ne sait pas lui associer une valeur. On peut en déduire que l’instruction explain plan ne donne pas la valeur de la variable à l’analyseur de requête.

(Ma) Conclusion

Si explain plan ne produit pas le plan d’exécution qui sera celui de la requête lors de la concommitance de bind variables et d’histogrammes c’est parce que la valeur de la variable n’est pas transmise au CBO et de fait celui-ci doit se rabattre sur un calcul qui suppose qu’au travers de cette bind variable n’importe quelle valeur peut être passée. De là il calcule une cardinalité moyenne qui l’oriente à bon ou mauvais escient sur un plan qui ne représente pas toujours la réalité.

Dans ces conditions peut-on compter sur explain plan pour faire du tuning ? Je dirais oui, le but d’explain plan est de répondre à la question qu’on lui pose, en l’occurrence et dans tout ce billet, “quel plan prendrait l’optimiseur si on le contraignait avec une valeur variable sur la colonne C1”, la réponse qu’il donne, à savoir “un parcours complet de la table” est vraie pour des valeurs de la variable de 0 à 7 (8 valeurs) et fausse si la variable est comprise entre 8 et 14 (7 valeurs) où le parcours par index est moins coûteux.

Si on souhaite connaitre le plan que l’optimiseur utiliserait pour une valeur précise, il faut la lui donner et ne pas passer par une variable.

On est loin de l’ésotérique absence de hard parse ou de la tout aussi surprenante perte d’information lors d’une conversion de type et quelque part ça rassure non ?


Blogographie

Tom Kyte: When the explanation doesn’t sound quite right…

Explain plan, Bind variables et Histogrammes (1/2)

Lors d’une discussion sur un forum j’ai lancé l’affirmation suivante qui a laissé un membre dubitatif :

Vous utilisez ici des bind variables. Lorsque vous utilisez ce type de variable dans un explain plan, Oracle ignore les éventuels histogrammes sur les colonnes alors que lors d’une exécution réelle il les prends en compte.

Je reprends ici les éclaircissements donnés à ce sujet sur ledit forum.

Mise en place d’un environnement de test

Je me suis créé une table de test de 100 000 lignes contenant une colonne qui suit une loi normale de la manière suivante :

set autot off

drop table t1;
create table t1 ( c1 number );

insert /*+ APPEND */ into t1
select abs(ceil(dbms_random.normal*3))
from sys.dual
connect by rownum 'FOR COLUMNS C1 SIZE 254', estimate_percent=>100)

J’ai donc la distribution suivante pour les valeurs de ma table

select c1, count(*)
from t1
group by c1
order by c1 ;

        C1   COUNT(*)
---------- ----------
         0      12917
         1      24741
         2      21291
         3      16074
         4      11194
         5       6775
         6       3840
         7       1778
         8        868
         9        346
        10        113
        11         51
        12         10
        13          2

Tests avec explain plan

Premier test avex une valeur fortement représentée :

variable s number

exec :s := 1 ;

explain plan for
select /* tst explain 1 */ * from t1
where c1=:s ;

@?/rdbms/admin/utlxplp

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
Plan hash value: 4291296153

-------------------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT |      |  7143 | 21429 |    14   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| I_T1 |  7143 | 21429 |    14   (0)| 00:00:01 |
-------------------------------------------------------------------------

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

   1 - access("C1"=TO_NUMBER(:S))

13 rows selected.

Le plan d’exécution m’indique que je vais remonter 7143 lignes. Je sais que c’est faux, la distribution des données me donne 24741 et l’histogramme ayant été calculé complètement, il donne les mêmes inforamtions à l’optimiseur.

Je fais un nouveau test avec une valeur très faiblement représentée

exec :s := 13 ;

explain plan for
select /* tst explain 13 */ * from t1
where c1=:s ;

@?/rdbms/admin/utlxplp
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
Plan hash value: 4291296153

-------------------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT |      |  7143 | 21429 |    14   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| I_T1 |  7143 | 21429 |    14   (0)| 00:00:01 |
-------------------------------------------------------------------------

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

   1 - access("C1"=TO_NUMBER(:S))

13 rows selected.

Là encore le plan m’annonce que je vais remonter 7143 lignes, hors je sais que j’en remonterai deux

Tests d’exécution

Je refais mon test pour la valeur 13 qui doit remonter deux lignes

select /* tst exec 13 */ * from t1
where c1=:s ;

        C1
----------
        13
        13

Formidable, elle remonte bien deux lignes, l’explain plan m’avait donc bien donné une information erronée. Que dit le plan choisi par le CBO ?

select * from table( dbms_xplan.display_cursor() ) ;

------------------------------------------------------------------------------------
SQL_ID  31j5tck2g6gd2, child number 0
-------------------------------------
select /* tst exec 13 */ * from t1 where c1=:s

Plan hash value: 4291296153

-------------------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT |      |       |       |     1 (100)|          |
|*  1 |  INDEX RANGE SCAN| I_T1 |     2 |     6 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------

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

   1 - access("C1"=:S)


18 rows selected.

Vous me direz qu’à postériori il est simple de donner le nombre de lignes qui ont été ramenées … Ok, je refais la même requête en lui demandant de me mémoriser les lignes estimées par le CBO (E-ROWS) et les lignes effectivement remontées (A-ROWS) et je refais afficher le plan.

select /*+ GATHER_PLAN_STATISTICS */ * from t1
where c1=:s ;

        C1
----------
        13
        13


select * from table( dbms_xplan.display_cursor(FORMAT=>'ALLSTATS LAST'));SQL>

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
SQL_ID  5av85qaqqqynu, child number 0
-------------------------------------
select /*+ GATHER_PLAN_STATISTICS */ * from t1 where c1=:s

Plan hash value: 838529891

------------------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |        |      2 |00:00:00.01 |     156 |
|*  1 |  TABLE ACCESS FULL| T1   |      1 |      2 |      2 |00:00:00.01 |     156 |
------------------------------------------------------------------------------------

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

   1 - filter("C1"=:S)


18 rows selected.

Ok, il estme donc bien deux lignes et en retourne 2 … ça parait logique et ça rassure

Pourquoi 7143 ?

On a vu qu’un explain plan produisait systématiquement la valeur erronée 7143, d’où la tire-t-il ? Je suppose qu’il utilise la formule de cardinalité générale sans tenir compte des histogrammes :

cardialité=nombre de lignes non nulles / nombre de valeurs distinctes ce qui dans notre cas donne : 100 000 lignes non nulles pour 14 valeurs distinctes … Je vous laisse compter le temps de taper la requête :

select round(100000/14) est from dual;

       EST
----------
      7143

Donc l’explain plan ne s’est pas donné la peine de parcourir l’histogramme.

Une question reste en suspens : Pourquoi la variable n’est pas interprétée ? … Suite au prochain épisode, ça se complexifie …

Paris Oracle Meetup à Lille: Super Cluster et hébergement Cloud

meetup_logo-500x370

Super Cluster et hébergement Cloud: Le groupe Paris Oracle Meetup, animé entr’autres par Laurent Leturgez, Yves Colin et Olivier Bernhard tous trois très impliqués dans l’organisation des évènements du groupe, propose une double rencontre avec Philippe Fierens ( Oracle ACE ) qui parlera de super cluster et Laurent Leturgez qui évoquera les meilleurs emplacements du web où faire héberger son cloud Oracle, l’occasion de voir si son analyse rejoint celle de Tim Hall qui a aussi donné son opinion sur le sujet récemment au sein de ce même groupe.

Une bonne occasion de rejoindre le groupe Paris Oracle Meetup, de participer et de rencontrer d’autres passionnés des applications Oracle.

Dernier détail, et non des moindres, c’est à Lille, on s’en doutait c’est écrit dans le titre mais ça va mieux en le disant certes, mais surtout ces meetups sont tous GRATUITS ce qui de nos jours est de plus en plus rare. Profitez !

Purger les problèmes dans ADRCI

Le souci

J’utilise la commande purge d’adrci mais mes problèmes restent présents

La commande

adrci> delete from PROBLEM

L’autre souci

L’autre souci c’est que ça purge tous les problèmes et que j’aimerais bien garder ceux qui sont “récents”.

C’est pas interdit de demander de l’aide …

adrci> help delete

  Usage: DELETE FROM <relation> [WHERE <predicate_string>]
       Arguments:
         <relation_name>: Name of the relation to delete from.

      [WHERE <predicate_string>]: The predicate string must be double-quoted.

      Examples:
       delete from incident where 'incident_id > 1'
adrci> show problems

ADR Home = /opt/oracle/diag/rdbms/dbun/DBNAME:
*************************************************************************
PROBLEM_ID           PROBLEM_KEY                                                 LAST_INCIDENT        LASTINC_TIME
-------------------- ----------------------------------------------------------- -------------------- ----------------------------------------
1                    ORA 600 [psdnopGetACL-4]                                    81105                2016-03-27 03:53:10.648000 +02:00
2                    ORA 600 [ktcccod-1]                                         3960130              2016-04-22 06:25:53.408000 +02:00
3                    ORA 600 [kdBlkCheckError]                                   8600040              2016-05-24 11:14:17.489000 +02:00

Allons-y essayons

adrci> delete from PROBLEM where lastinc_time < to_date('2016-03-27 04:00:00', 'YYY-MM-DD HH24:MI:SS' )
DIA-48260: Function to_date not found

Mince, c’est pas du vrais SQL Oracle avec les fonctions qui vont bien !

adrci> delete from PROBLEM where lastinc_time < sysdate-60
DIA-48230: Expression arguments must match types
DIA-48249: Type mismatch - lhs = 7, rhs = 6, result = 8, op = 0

Mince, ça ne sait pas transtyper !

adrci> delete from PROBLEM where lastinc_time < systimestamp - 60

1 Row Deleted

adrci> show problems

ADR Home = /opt/oracle/diag/rdbms/dbun/DBNAME:
*************************************************************************
PROBLEM_ID           PROBLEM_KEY                                                 LAST_INCIDENT        LASTINC_TIME
-------------------- ----------------------------------------------------------- -------------------- ----------------------------------------
2                    ORA 600 [ktcccod-1]                                         3960130              2016-04-22 06:25:53.408000 +02:00
3                    ORA 600 [kdBlkCheckError]                                   8600040              2016-05-24 11:14:17.489000 +02:00
2 rows fetched

C’était pourtant pas si compliqué

Tuer une tâche datapump qui n’a plus son fichier dump sur disque

Le souci

Mon export datapump s’est planté, j’ai effacé mon fichier de dump et donc plus moyen de faire l’attachement qui me permettrait de tuer la tâche en faisant kill_job. Pire encore, quand je tente le coup, datapump me donne un message d’erreur qui n’a pas gtrand chose à vois avec la problème réel.

[oracle@serveur ~]$ impdp toto/lolo attach=SYS_IMPORT_TRANSPORTABLE_01

Import: Release 11.2.0.4.0 - Production on Thu Apr 7 09:45:57 2016

Copyright (c) 1982, 2011, Oracle and/or its affiliates.  All rights reserved.

Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning option
ORA-31626: job does not exist
ORA-06512: at &amp;quot;SYS.DBMS_SYS_ERROR&amp;quot;, line 79
ORA-06512: at &amp;quot;SYS.KUPV$FT&amp;quot;, line 405
ORA-31638: cannot attach to job SYS_IMPORT_TRANSPORTABLE_01 for user TOTO
ORA-31632: master table &amp;quot;TOTO.SYS_IMPORT_TRANSPORTABLE_01&amp;quot; not found, invalid, or inaccessible
ORA-00942: table or view does not exist

(suite…)

Arrondir une date

On est souvent confronté à la problématique suivante, comment arrondir une date en dehors des sentiers tracés par trunc (la minute, l’heure, la semaine …) et donc stackoverflow donne ces exemples de réponses pour arrondir une date à 10 minutes mais qui sont facilement extrapolables à d’autres intervals :

Le plus compréhensible

SELECT
  sysdate, TRUNC(sysdate, 'MI') - MOD(TO_CHAR(sysdate, 'MI'), 10) / (24 * 60)
FROM dual;

Le moins lisible

select
  trunc(sysdate, 'mi') - numtodsinterval(mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 10), 'minute')
from dual;

Le plus à jour

select
  trunc(sysdate, 'mi') 
  - mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 10) / (24 * 60)
from dual;

Au niveau du coût pour les 3 on a (avec un temps d’execution identique inférieur au centième de seconde) :

-----------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT |      |     1 |     2   (0)| 00:00:01 |
|   1 |  FAST DUAL       |      |     1 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        657  bytes sent via SQL*Net to client
        520  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

Un exemple ?

alter session set NLS_DATE_FORMAT='YY-MM-DDHH24:MI:SS' ;

col DT   for a8 wrap
col S10  for a8 wrap
col M    for a8 wrap
col M5   for a8 wrap
col M10  for a8 wrap
col M15  for a8 wrap
col M30  for a8 wrap
col H2   for a8 wrap
col H3   for a8 wrap
col H6   for a8 wrap
col H8   for a8 wrap
col H12  for a8 wrap
col DAY  for a8 wrap
col WEEK for a8 wrap

select sysdate                                                                                     DT
     , sysdate - mod(EXTRACT(second FROM cast(sysdate as timestamp)), 10) / (24 * 60 * 60)         S10
     , trunc(sysdate, 'mi')                                                                        M
     , trunc(sysdate, 'mi') - mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 5) / (24 * 60)  M5
     , trunc(sysdate, 'mi') - mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 10) / (24 * 60) M10
     , trunc(sysdate, 'mi') - mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 15) / (24 * 60) M15
     , trunc(sysdate, 'mi') - mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 30) / (24 * 60) M30
     , trunc(sysdate, 'hh24') - mod(EXTRACT(hour FROM cast(sysdate as timestamp)), 2) / 24         H2
     , trunc(sysdate, 'hh24') - mod(EXTRACT(hour FROM cast(sysdate as timestamp)), 3) / 24         H3
     , trunc(sysdate, 'hh24') - mod(EXTRACT(hour FROM cast(sysdate as timestamp)), 6) / 24         H6
     , trunc(sysdate, 'hh24') - mod(EXTRACT(hour FROM cast(sysdate as timestamp)), 8) / 24         H8
     , trunc(sysdate, 'hh24') - mod(EXTRACT(hour FROM cast(sysdate as timestamp)), 12) / 24        H12
     , trunc(sysdate, 'dd')                                                                        DAY
     , trunc(sysdate, 'd')                                                                         WEEK
from dual
/
DT S10 M M5 M10 M15 M30 H2 H3 H6 H8 H12 DAY WEEK
18-05-14
17:28:39
18-05-14
17:28:30
18-05-14
17:28:00
18-05-14
17:25:00
18-05-14
17:20:00
18-05-14
17:15:00
18-05-14
17:00:00
18-05-14
16:00:00
18-05-14
15:00:00
18-05-14
12:00:00
18-05-14
16:00:00
18-05-14
12:00:00
18-05-14
00:00:00
18-05-13
00:00:00

C’était pourtant pas si compliqué

Créer un package ADRCI

[oracle@serveur ~/work]$ adrci

ADRCI: Release 11.2.0.4.0 - Production on Thu Jan 7 14:14:24 2016

Copyright (c) 1982, 2011, Oracle and/or its affiliates.  All rights reserved.

ADR base = "/opt/oracle"
adrci> show incident

ADR Home = /opt/oracle/diag/rdbms/titi/titi:
*************************************************************************
0 rows fetched

ADR Home = /opt/oracle/diag/rdbms/serveur/DB:
*************************************************************************
INCIDENT_ID          PROBLEM_KEY                                                 CREATE_TIME
-------------------- ----------------------------------------------------------- ----------------------------------------
208129               ORA 600 [ktcccod-1]                                         2016-01-05 06:45:45.367000 +01:00
592465               ORA 600 [kdBlkCheckError]                                   2016-01-07 11:27:32.712000 +01:00
592481               ORA 600 [kdBlkCheckError]                                   2016-01-07 11:29:22.188000 +01:00
592466               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:08:52.179000 +01:00
592457               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:11:18.073000 +01:00
592449               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:22:53.246000 +01:00
592467               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:57:21.733000 +01:00
592441               ORA 600 [kdBlkCheckError]                                   2016-01-07 13:20:34.972000 +01:00
592442               ORA 600 [kdBlkCheckError]                                   2016-01-07 13:36:02.718000 +01:00
592443               ORA 600 [kdBlkCheckError]                                   2016-01-07 13:43:26.175000 +01:00



adrci> IPS CREATE PACKAGE PROBLEM 592443
DIA-48448: This command does not support multiple ADR homes


adrci> show homes
ADR Homes:
diag/rdbms/titi/titi
diag/rdbms/serveur/DB

adrci> SET HOME diag/rdbms/serveur/DB
adrci> show incident

ADR Home = /opt/oracle/diag/rdbms/serveur/DB:
*************************************************************************
INCIDENT_ID          PROBLEM_KEY                                                 CREATE_TIME
-------------------- ----------------------------------------------------------- ----------------------------------------
208129               ORA 600 [ktcccod-1]                                         2016-01-05 06:45:45.367000 +01:00
592465               ORA 600 [kdBlkCheckError]                                   2016-01-07 11:27:32.712000 +01:00
592481               ORA 600 [kdBlkCheckError]                                   2016-01-07 11:29:22.188000 +01:00
592466               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:08:52.179000 +01:00
592457               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:11:18.073000 +01:00
592449               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:22:53.246000 +01:00
592467               ORA 600 [kdBlkCheckError]                                   2016-01-07 12:57:21.733000 +01:00
592441               ORA 600 [kdBlkCheckError]                                   2016-01-07 13:20:34.972000 +01:00
592442               ORA 600 [kdBlkCheckError]                                   2016-01-07 13:36:02.718000 +01:00
592443               ORA 600 [kdBlkCheckError]                                   2016-01-07 13:43:26.175000 +01:00
10 rows fetched

adrci> IPS CREATE PACKAGE PROBLEMKEY "ORA 600 [kdBlkCheckError]"
Created package 1 based on problem key ORA 600 [kdBlkCheckError], correlation level typical

adrci> show problem

ADR Home = /opt/oracle/diag/rdbms/serveur/DB:
*************************************************************************
PROBLEM_ID           PROBLEM_KEY                                                 LAST_INCIDENT        LASTINC_TIME
-------------------- ----------------------------------------------------------- -------------------- ----------------------------------------
1                    ORA 600 [ktcccod-1]                                         208129               2016-01-05 06:45:45.367000 +01:00
2                    ORA 600 [kdBlkCheckError]                                   592443               2016-01-07 13:43:26.175000 +01:00
2 rows fetched

adrci> IPS ADD FILE /opt/oracle/diag/rdbms/serveur/DB/trace/DB_s001_217994.trc PACKAGE 2
DIA-49404: No such package [2]

adrci> IPS SHOW PACKAGE
   PACKAGE_ID             1
   PACKAGE_NAME           ORA600kdB_20160107142052
   PACKAGE_DESCRIPTION
   DRIVING_PROBLEM        2
   DRIVING_PROBLEM_KEY    ORA 600 [kdBlkCheckError]
   DRIVING_INCIDENT       592465
   DRIVING_INCIDENT_TIME  2016-01-07 11:27:32.712000 +01:00
   STATUS                 New (0)
   CORRELATION_LEVEL      Typical (2)
   PROBLEMS               1 main problems, 1 correlated problems
   INCIDENTS              6 main incidents, 2 correlated incidents
   INCLUDED_FILES         41

adrci> IPS GENERATE PACKAGE 1 IN /opt/oracle/work
Generated package 1 in file /opt/oracle/work/ORA600kdB_20160107142052_COM_1.zip, mode complete
adrci> exit