mardi 23 novembre 2010

Prototype d'un pool de connexion au niveau du client

/* Compilation: make -f demo_proc.mk EXE=test_rac_recette_con_cache OBJS=test_rac_recette_con_cache.o build PROCFLAGS="sqlcheck=full userid=xxx/xxx@xxx code=ansi_c define=V8" */

#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
#include unistd.h

#include sys/time.h
#include sys/types.h
#include sys/wait.h
#include sys/ipc.h
#include sys/shm.h
#include sys/sem.h

#include sqlca.h
#include sqlcpr.h

#define UNAME_LEN 30
#define PWD_LEN 30
#define CONNECT_STRING_LEN 128
#define NB_CONNEXION 50

typedef int SEMAPHORE;

/* Contexte de connexion */
EXEC SQL BEGIN DECLARE SECTION;
typedef struct {
    sql_context sql_cntxt[NB_CONNEXION];
    short h_dspnb[NB_CONNEXION];
} sCntxtConnexion;

sql_context sql_cntxt;

VARCHAR username[UNAME_LEN];
VARCHAR password[PWD_LEN];
VARCHAR connect_string[CONNECT_STRING_LEN];
EXEC SQL END DECLARE SECTION;

sCntxtConnexion *p_cntx;

SEMAPHORE sem;

void sql_error();
int trait_rqt(int i);

int detruire_sem(SEMAPHORE sem);
int changer_sem(SEMAPHORE sem, int val);
SEMAPHORE creer_sem(key_t key);
void P(SEMAPHORE sem);
void V(SEMAPHORE sem);

int main(int argc, char* argv[]) {
int i,j;
int nb_process;
pid_t pid;
int status;

key_t cle, cle_sem;
int id;

int cpt;

if (argc != 5) { printf("test_rac_recette_con   ... \n"); return 0; }

printf("Pere: user %s mot de passe %s alias %s \n", argv[1], argv[2], argv[3]);

strcpy((char *) username.arr, argv[1]);
username.len = strlen((char *) username.arr);
strcpy((char *) password.arr, argv[2]);
password.len = strlen((char *) password.arr);
strcpy((char *) connect_string.arr, argv[3]);
connect_string.len = strlen((char *) connect_string.arr);

EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--\n");

nb_process = atoi(argv[4]);
j = 1;

EXEC SQL ENABLE THREADS;

/* Création du segment de mémoire partagée */
cle = ftok(getenv("HOME"), 'A');
if (cle == -1) {
    printf("Pere: pb ftok \n");
    return -1;
}
   
/* 0666: droits */
/* ipcrm -m */
id = shmget(cle, sizeof(sCntxtConnexion), IPC_CREAT | IPC_EXCL | 0666);
if (id == -1) {
    switch (errno) {
       case EEXIST:
      printf("Pere: le segment existe deja \n");
       default:
          printf("Pere: shmget \n");
          return -1;
    }
}

p_cntx = (sCntxtConnexion *) shmat(id, NULL, SHM_R | SHM_W);
if (p_cntx == NULL) {
    printf("Pere: shmat \n");
    return -1;
}

/* Création du pool */
for i in 0..NB_CONNEXION
-1
     EXEC SQL CONTEXT ALLOCATE :sql_cntxt;
     EXEC SQL CONTEXT USE :sql_cntxt;
     EXEC SQL CONNECT :username IDENTIFIED BY :password USING :connect_string;
     p_cntx->sql_cntxt[i] = sql_cntxt;
     p_cntx->h_dspnb[i]=0;

system("ipcs -m");

/* Test du pool sans fork */
sql_cntxt = p_cntx->sql_cntxt[0];
EXEC SQL CONTEXT USE :sql_cntxt;
EXEC SQL select count(*) into :cpt from alphacompactee;
printf("Pere: cpt: %d \n", cpt);

/* Création d'un sémaphore */
cle_sem = ftok(getenv("HOME"), 'B');
if (cle_sem == -1) {
    printf("Pere: pb ftok \n");
    return -1;
}

sem = creer_sem(cle_sem);

printf("Semaphore: \n");
system("ipcs -s | grep snotter");


/* Fork des processus fils */
for(i = 0; i < nb_process; i++) {
    pid = fork();
    if (pid == 0) {
        /* printf("Fils: %d \n", i); */
        trait_rqt(i);
        return 0;
    }
}

for(i = 0; i < nb_process; i++) {
    wait(&status);
}

printf("Pere: fin des fils ... \n");


/* Libération du pool */
for i in 0..NB_CONNEXION-1
     sql_cntxt = p_cntx->sql_cntxt[i];
     EXEC SQL CONTEXT USE :sql_cntxt;
     /*EXEC SQL COMMIT RELEASE;*/
     EXEC SQL CONTEXT FREE :sql_cntxt;

/* Suppression du segment de mémoire partagée */
/* (char *) */
if (shmdt((void *) p_cntx) == -1) {
    printf("Pere: shmdt \n");
    return -1;
}

if (shmctl(id, IPC_RMID, NULL) == -1) {
    printf("Pere: shmctl(remove) \n");
    return -1;
}


/* Suppression du sémaphore */
if (detruire_sem(sem) == -1) {
    printf("Pere: detruire_sem \n");
    return -1;
};

return 0;

}

int trait_rqt(int i) {
int j;
hrtime_t point1, point2;
char tcHeure[20];
struct timeval tv;
struct tm *tm;

int cpt;

gettimeofday(&tv);
tm=localtime(&tv.tv_sec);
memset(tcHeure, '\0', sizeof(tcHeure));
sprintf(tcHeure, " %d:%02d:%02d %03d ", tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec/1000);

point1 = gethrtime();


/* Attente sur le sémaphore */
printf("ATT Fils %d wait \n", i);
P(sem);


/* Choix d'une connexion disponible */

for j in 0..NB_CONNEXION-1
  if (p_cntx->h_dspnb[j]==0) {
      p_cntx->h_dspnb[j]=1;
      break;
  }
  else {
      printf("Fils %d Conn %d deja prise \n", i, j); 
  }
  

printf("Fils %d Conn %d prise \n", i, j);

/* Libération du sémaphore */
V(sem);

point2 = gethrtime();
printf("Fils Conn %d; %s; %lld ; %lld ; %lld \n", i, tcHeure, (point2 - point1)/1000000, point1, point2);

gettimeofday(&tv);
tm=localtime(&tv.tv_sec);
memset(tcHeure, '\0', sizeof(tcHeure));
sprintf(tcHeure, " %d:%02d:%02d %03d ", tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec/1000);

point1 = gethrtime();

sql_cntxt = p_cntx->sql_cntxt[j];
EXEC SQL CONTEXT USE :sql_cntxt;

/* Exécution d'une requête */
EXEC SQL select count(*) into :cpt from alphacompactee;

point2 = gethrtime();

printf("Fils Req %d; %s; %lld ; %lld ; %lld \n", i, tcHeure, (point2 - point1)/1000000, point1, point2);

/* Attente sur le sémaphore */

printf("ATT Fils %d wait \n", i);
P(sem);
 

/* Libération de la connexion */
p_cntx->h_dspnb[j]=0;
 

/* Libération du sémaphore */
printf("Fils %d Conn %d liberee\n", i, j);
V(sem);

return 0;
}
 
void sql_error(char *msg) {
char err_msg[128];
int buf_len, msg_len;

EXEC SQL WHENEVER SQLERROR CONTINUE;

printf("\n%s\n", msg);
buf_len = sizeof (err_msg);
 sqlglm(err_msg, (unsigned int *)&buf_len,(unsigned int *)&msg_len);
if (msg_len > buf_len)
    msg_len = buf_len;
printf("%.*s\n", msg_len, err_msg);
EXEC SQL ROLLBACK RELEASE;
exit(1);

}

int detruire_sem(SEMAPHORE sem) {
if (semctl(sem, 0, IPC_RMID, 0) != 0) {
    printf("detruire_sem \n");
    return -1;
}
return 0;
}

int changer_sem(SEMAPHORE sem, int val) {
struct sembuf sb[1];

sb[0].sem_num = 0;
sb[0].sem_op = val;
sb[0].sem_flg = 0;
if (semop(sem, sb, 1) != 0) {
    printf("changer_sem \n");
    return -1;
}
return 0;
}

SEMAPHORE creer_sem(key_t key) {
SEMAPHORE sem;
int r;
union semun {
int val;
struct semid_ds *buf;
ushort *array;
} s_ctl;

sem = semget(key, 1, IPC_CREAT | 0666);
if (sem < 0) {            
    printf("creer_sem \n");
    exit(EXIT_FAILURE);
}

s_ctl.val =  1;  
r = semctl (sem, 0, SETVAL, s_ctl );
if (r < 0) {
    printf("initialisation sémaphore \n");
    exit(EXIT_FAILURE);
}
return sem;
}

void P(SEMAPHORE sem) {
changer_sem(sem, -1);
}

void V(SEMAPHORE sem) {
changer_sem(sem, 1);
}

mardi 5 octobre 2010

Problème de connexion

Soit un client PRO*C qui a épisodiquement des temps de connexion à une instance Oracle de l'ordre de quelques centaines de ms, voire plusieurs secondes.

La première étape consiste à isoler le problème. Dans le cas présent, la latence est observée au moment de l'exécution de l'instruction CONNECT.

Pour améliorer les temps de connexion, deux paramètres peuvent être intéressants à modifier:
- SDU ( http://www.sun.com/blueprints/1002/817-0370-10.pdf );
- tcp.nodelay ( désactivation de l'algorithme de Nagle lié à la couche TCP ).

Pour aller plus loin, consulter le guide d'administration sur la couche sqlnet.

Si le problème n'est pas résolu, on peut alors poser  les traces sqlnet sur le client et ensuite éventuellement sur le listener, voire le dispatcher en cas d'utilisation du mode shared server. Pour les traces sqlnet sur le client, on peut utiliser une configuration spécifique ( sqlnet.ora + tnsnames.ora ) via la variable d'environnement TNS_ADMIN. Le paramétrage des traces du listener s'effectue dans le fichier listener.ora. Puis, on lance le listener pour activer le mode trace ( exemple: set trc_level support ). L'étude du dispatcher se fait via l'event 10248.

Dans le cas présent, aucune erreur ( nserror ) n'a été détectée.  Une analyse réseau ( tcpdump sur le client et un noeud du RAC, utilisation de sondes ) a ensuite été menée, ce qui a permis de localiser le problème au niveau de la machine cliente: des paquets de type ACK retardé ou donnée ne sont pas émis immédiatement. Il est à noter que les adresses virtuelles des noeuds du RAC sont résolues directement dans le fichier host. 


En changeant de client Oracle ( 9i -> 10g ), le problème a persisté, d'où une mise en cause de la couche système, en particulier au niveau de son paramétrage TCP. Une analyse via l'outil truss n'a pas permis d'obtenir de résultats probants. L'outil Dtrace ne peut pas être utilisé, la machine cliente étant en Solaris 8.

Quelques modifications ont été effectuées au niveau du paramétrage TCP du système, sans grand succès ( exemple: tcp_time_wait_interval ). Pour dédouaner complètement le client Oracle, un client/serveur de type écho à base de socket TCP a été développé ( serveur sur un noeud du RAC ) en se basant sur les sources disponibles à cette adresse : http://cs.baylor.edu/~donahoo/practical/CSockets/textcode.html. Une fois ce dernier installé, on a pu observer que la durée de traitement moyenne de la commande recv du client était de l'ordre de quelques dizaines de micros pour un échange de 8 ko. Cependant, de temps en temps, elle peut excéder la dizaine de ms.

Suite à cette constatation, le problème est étudié par l'équipe système, qui ne trouve pas de solution.  

Lors de ces tests, la charge transactionnelle était faible ( 4 transactions par seconde ), d'où une solution possible. Si la charge augmente ou pour éviter ce type de problème sur une machine mal configurée, on peut mettre en place un pool de sockets dédiées aux connexions Oracle. Le principe est le suivant:
- Lors du démarrage du serveur d'application, on crée les sockets entre le client Oracle et l'instance, puis on met en mémoire les pointeurs de connexion dans un segment de mémoire partagée, ce dernier étant protégé par un sémaphore;
- Lors du traitement d'une transaction, le serveur crée un processus fils qui va rechercher dans le segment de mémoire partagée une connexion disponible, si le sémaphore le permet ( unicité d'accès à la ressource ). Une fois une connexion sélectionnée ( temps d'exécution inférieur à 1 ms ), la requête est exécutée et les données transmises au client. La déconnexion, quant à elle, se limite à rendre de nouveau disponible la connexion utilisée.
- Lors de l'arrêt du serveur, les sockets sont closes et les ressources système libérées.

De cette manière, la discontinuité de performance se réduit à l'échange des données entre le client et l'instance après l'exécution d'une requête. Les temps de connexion/déconnexion, quant à eux, sont considérés comme négligeables.

mardi 24 août 2010

Installation 11gR2 sur Ubuntu ( partie 2 )

Lors du redémarrage d'Ubuntu, j'ai rencontré un problème avec OEM ( Oracle Entreprise Manager ), la console d'administration. L'erreur était la suivante: OC4J Configuration issue. /u01/app/oracle/product/11.2.0/dbhome_1/oc4j/j2ee/OC4J_DBConsole_$HOSTNAME_orcl not found.

Pour corriger ce problème, j'ai effectué les opérations suivantes:
- Suppression du repository d'OEM:  emca -deconfig dbcontrol db -repos drop;
- export ORACLE_HOSTNAME=$HOSTNAME;
- Recréation du repository: emca -config dbcontrol db -repos create.

Pour démarrer OEM, on utilise la commande suivante: emctl start dbconsole ( arrêt: emctl stop dbconsole ). Sous Ubuntu, on obtient alors le message suivant: ulimit: 25: bad number. Il peut être ignoré.

En plus de l'outil d'administration, on dispose du client de développement habituel, sqldeveloper ( commande: sh sqldeveloper.sh ). Il peut fonctionner avec OpenJDK ( commande: java -version ).

Pour éviter une fenêtre blanche ( gnome ), il faut désactiver les effets visuels dans la personnalisation de l'apparence du bureau.

mercredi 28 juillet 2010

Installation 11gR2 sur Ubuntu ( partie 1 )

Dernièrement, j'ai acheté un portable Linux ( 64 bits, Ubuntu LTS version 10.04 ) chez Novatux. Pour une fois, je vais faire un peu de pub, car le support technique y est remarquable ( merci Laurent ).

Sur ce dernier, je viens d'installer une 11gR2 en m'appuyant sur les liens suivants:
- http://www.pythian.com/news/2329/installing-oracle-11gr1-on-ubuntu-904-jaunty-jackalope/
- http://www.pythian.com/news/13291/installing-oracle-11gr2-enterprise-edition-on-ubuntu-10-04-lucid-lynx/

L'installation ( instance + database + OEM ) s'est bien déroulée. Quelques conseils:
- Bien respecter la phase préparatoire ( installation des librairies manquantes, création de l'utilisateur oracle, paramétrage système ... );
- Installer le package sux avant de lancer le binaire runinstaller et positionner la variable DISPLAY à :0.0;
- Ne pas tenir compte des erreurs relatives aux packages, OUI  ( Oracle Universal Installer ) ne prenant pas en compte Ubuntu;
- Vérifier au préalable votre configuration graphique, vous pouvez en effet rencontrer une fenêtre blanche lors de l'installation et avoir quelques difficultés pour la valider.

jeudi 1 juillet 2010

Analyse d'une requête

Ce lien donne une liste d'outils permettant d'effectuer un diagnostic sur une requête SQL: http://oracle-randolf.blogspot.com/2009/02/basic-sql-statement-performance.html.

jeudi 13 mai 2010

Shared server

Dans un environnement Solaris, j'ai dernièrement testé une connexion à une instance configurée en mode shared server. Pour information, l'instance appartient à un RAC 10g à 2 noeuds. Les clients sont écrits en PRO*C et se connectent à un dispatcher via un client Oracle 9i.

Les résultats sont les suivants:
- La durée de connexion est comprise en moyenne entre 20 à 25 ms.
- La durée de lecture d'une ligne d'une table de 20 colonnes ( lecture utilisant un index de la table ) est comprise entre 7 à 12 ms.
- La durée de lecture d'une ligne d'une table de 120 colonnes ( lecture utilisant un index de la table ) est comprise entre 11 à 16 ms.

Pour aller plus loin:
- Une description du mode shared server.
- Une description sur les connexions Oracle.

dimanche 2 mai 2010

SPM ( SQL Plan Management )

Quelques liens:
- http://optimizermagic.blogspot.com/search/label/sql plan management
- http://www.oracle-base.com/articles/10g/AutomaticSQLTuning10g.php#sql_tuning_sets
- http://www.oracle-base.com/articles/11g/AutomaticSqlTuning_11gR1.php  

Ce nouveau concept de la 11g permet de figer les plans d'exécution comme les stored outlines. De plus, il collecte pour une requête un meilleur plan d'exécution si l'optimiseur  a trouvé un chemin d'accès plus performant pour récupérer les données. De cette manière, on est désormais à l'abri des changements brusques des plans d'exécution ( qui n'a pas connu cela sur une base de production ? ), tout en pouvant améliorer les temps d'exécution des requêtes de manière contrôlée.

mardi 30 mars 2010

Méthode de détection d'un problème ( partie 2 )

Si les vues associées aux wait events ne donnent aucun indice, on peut alors étudier les statistiques d'une session. Pour ce faire, on utilise les vues v$sesstat et v$statname. Pour information, une description succincte des statistiques est disponible dans une annexe du document de référence ( 11g: http://download.oracle.com/docs/cd/B28359_01/server.111/b28320/stats.htm#i29468 ).

L'outil de T.Poder, snapper, permet entre autres de visualiser les statistiques d'une session. Il est décrit à cette adresse: http://tech.e2sn.com/oracle-scripts-and-tools/session-snapper.

Par ailleurs, vous pouvez analyser un problème à partir des outils d'Oracle ( statspack ou awr ). Ce lien http://jonathanlewis.wordpress.com/statspack-examples/ vous aidera à interpréter ce type de rapport. L'outil ADDM permet également de recueillir quelques pistes d'optimisation. 

Si tous ces outils ne vous permettent pas de résoudre votre problème, il reste alors à utiliser un outil comme dtrace ( http://hub.opensolaris.org/bin/view/Community+Group+dtrace/WebHome ), l'idée étant de descendre au niveau de la couche système pour détecter l'origine du problème.

dimanche 28 mars 2010

Cardinality feedback

Quelques liens:
- http://dioncho.wordpress.com/2009/12/17/trivial-research-on-the-cardinality-feedback-on-11gr2/
- http://wedostreams.blogspot.com/2009/12/hidden-undocumented-and-adaptive-cursor.html 
- http://jonathanlewis.wordpress.com/2009/12/16/adaptive-optimisation/ 

En se connectant en tant que SYS, vous pouvez visualiser les caractéristiques d'un paramètre caché de la manière suivante:
select ksppinm as param,
       y.ksppstvl as ses_val,
       z.ksppstvl as inst_val,
       decode(bitand(ksppiflg/256,1),1,'TRUE','FALSE') as ses_modif,
       decode(bitand(ksppiflg/65536,3),1, 'IMMEDIATE',2,'DEFERRED', 3,'IMMEDIATE','FALSE') as system_modif,
       ksppdesc as descr
from x$ksppi x, x$ksppcv y, x$ksppsv z
where x.indx = y.indx
  and x.indx = z.indx
  and ksppinm like '\_%' escape '\'
order by ksppinm;

dimanche 14 février 2010

Extended cursor sharing

Jusqu'à la version 11g, le mécanisme dit de peek bind variable posait parfois des problèmes lorsque la sélectivité des différentes valeurs des bind variables était différente. Désormais, le mécanisme dit d'extended cursor sharing permet de corriger ce défaut après quelques exécutions.

Pour illustrer ce nouveau mécanisme, on crée une nouvelle table:
create table adapt_curs_share (
   idf number,
  date_ins date,
  chaine varchar2(80),
  nombre number not null )
tablespace 'nom_tablespace';

create sequence seq_adapt_curs_share
increment by 1
start with 1
;  


create index idx_adapt_curs_share_nombre on adapt_curs_share(nombre) tablespace 'nom_tablespace';

On l'alimente de la manière suivante:
begin
   for i in 1..100000
   loop
      insert into adapt_curs_share
      (idf, date_ins, chaine, nombre)
      values
      (seq_adapt_curs_share.nextval, sysdate, dbms_random.string('P', 80), abs(mod(dbms_random.random(),1000 )));
   end loop;
  
   commit;
end;

On calcule les statistiques et on nettoie le shared pool :
dbms_stats.gather_table_stats(
ownname => 'schéma',
tabname => 'adapt_curs_share',
method_opt=>'for all columns size 1',
cascade => true);

alter system flush shared pool;

Avant d'ouvrir la session sous DOS,  il est souhaitable de positionner le bon jeu de caractères:

On positionne le niveau le plus élevé pour les statistiques afin de recueillir plus d'informations au niveau des plans d'exécution:
alter session set statistics_level = all;

Une fois la configuration mise en place, on exécute sous sqlplus les instructions suivantes:
variable n number
exec :n := 999
select count(idf) from adapt_curs_share where nombre < :n; ( résultat: 99893 )
exec :n :=3
select count(idf) from adapt_curs_share where nombre < :n; ( résultat: 308 )

Le plan d'exécution de la requête est le suivant: 

Le contenu de la bind variable est 999, la valeur utilisée par la première exécution de la requête. Etant donné la faible sélectivité de la requête, un access full est effectué sur la table adapt_curs_share. La deuxième requête, bien que très sélective, va utiliser le même plan d'exécution, d'où l'idée d'améliorer le mécanisme de peek bind variable via le concept d'extended cursor sharing.

Dans la vue v$sql, les colonnes is_bind_sensitive ( utilisation du peek bind variable ), is_bind_aware ( utilisation de l'extended cursor sharing ) et is_shareable ( curseur partagé ou non ) donnent pour ce curseur le triplet (Y, N, Y).

Suite à ces deux premières exécutions, on effectue les opérations suivantes:

exec :n :=3
select count(idf) from adapt_curs_share where nombre < :n;
exec :n :=999
select count(idf) from adapt_curs_share where nombre < :n;

Pour le curseur 9fyq0vr5fh4dn, on observe désormais dans la vue v$sql trois fils au lieu d'un. Le fils 0 a exécuté les deux premières requêtes. Il utilise le mécanisme du peek bind variable, mais pas celui de l'extended cursor sharing et il n'est plus partageable.

Le fils 1 a exécuté la troisième requête ( bind variable = 3 ). Il utilise le peek bind variable, l'extended cursor sharing et il est partageable. Son plan d'exécution est le suivant;

Le fils 2 a exécuté la quatrième requête ( bind variable = 999 ). Il utilise le peek bind variable, l'extended cursor sharing et il est partageable. Son plan d'exécution est le suivant;

Si on exécute de nouveau la requête avec les deux valeurs 3 et 999, seuls les fils 1 et 2 sont utilisés.

Pour aller plus loin sur ce nouveau concept lié à la 11g, vous pouvez aussi consulter les vues v$sql_cs_statistics, v$sql_cs_selectivity et v$sql_cs_histogram.

jeudi 28 janvier 2010

Méthode de détection d'un problème ( partie 1 )

Sur ce sujet, je vous conseille de consulter les différents messages de Tanel Poder et James Morle:
- http://jamesmorle.wordpress.com/2009/11/09/the-oracle-wait-interface-is-useless-sometimes-pt/
- http://blog.tanelpoder.com/2010/01/15/beyond-oracle-wait-interface-part-2/
- http://jamesmorle.wordpress.com/2010/01/18/the-oracle-wait-interface-is-useless-sometimes-part-3a/
- http://jamesmorle.wordpress.com/2010/02/16/the-oracle-wait-interface-is-useless-sometimes-part-3b/

Pour détecter un problème dans une instance Oracle, on peut commencer par utiliser l' interface des wait events.
Un wait event représente une attente d'un processus utilisateur ( session ) ou de l'instance ( PMON, SMON ... ). Elle peut être liée à l'accès à une ressource ( données du cache, verrous ... ) . Par exemple, une session qui a besoin d'un bloc d'index absent dans la SGA effectue une demande de lecture au système, puis attend la réception du bloc. Ce temps d'attente est un type de wait event, en l'occurence le wait event db file sequential read.
Les deux vues utiles sont v$session_wait ( niveau session ) et v$system_event ( niveau instance ). Le script sw.sql de T.Poder donne un exemple d'utilisation de la vue v$session_wait.

Une description détaillée des wait events est disponible à cette adresse: http://sites.google.com/site/embtdbo/wait-event-documentation.

L'event trace de type 10046 permet aussi de récupérer les wait events. Au niveau d'une session, on l'active de la manière suivante:
- alter session set timed_statistics = true; -- Activation des Extended SQL Trace
- alter session set max_dump_file_size = unlimited;
- alter session set events '10046 trace name context forever, level 8';
- alter session set events '10046 trace name context off';
Pour tracer une session autre que la sienne, on peut utiliser la méthode set_ev du package dbms_system ou l'outil de debug d'Oracle, oradebug.

On peut récupérer les traces dans le répertoire indiqué par le paramètre d'initialisation USER_DUMP_DEST. Le suffixe des fichiers trace est trc ou TRC. On peut faciliter l'identification du fichier trace via cette commande: alter session set tracefile_identifier = 'MonFichierTrace'. L'utilitaire tkprof permet de mettre en forme les fichiers trace.

Le chapitre 5 du livre de Cary Millsap, Optimizing Oracle Performance, constitue une introduction intéressante pour la compréhension des traces SQL étendues. Une mise à jour est disponible à cette adresse: http://method-r.com/downloads/doc_details/72-mastering-performance-with-extended-sql-trace.

Si nécessaire, l'analyse du problème peut être étendue à la couche système. Pour commencer, on utilise la commande vmstat pour avoir une vision générale du système ( CPU, mémoire virtuelle ). Puis, on peut étudier plus en détail les processus ou threads ( top, topas, prstat ), la CPU ( mpstat ), les interruptions (intrstat ), les appels système ( truss, strace ).

mardi 12 janvier 2010

Plan d'exécution ( partie 2 )

Une fois une requête exécutée, vous pouvez visualiser son plan d'exécution contenu dans la library cache, une partie du shared pool ( SGA ) via la commande suivante: select * from table(dbms_xplan.display_cursor('id_requete', 0));.

L'identifiant de requête ( sql_id ) est une chaîne de caractères ( exemple: dqbzw72kjmvhb ). On peut le retrouver à l'aide de la vue v$sql ( colonnes sql_text et sql_fulltext pour une recherche sur le texte de la requête, colonnes last_load_time et last_active_time pour la première ou dernière exécution ).

Pour utiliser le package dbms_xplan, il faut pouvoir lire les vues du catalogue. Pour ce faire, l'instruction est la suivante: grant select_catalog_role to 'utilisateur';.

Pour information, la fonction display_cursor est une encapsulation des vues v$sql_plan et v$sql_plan_statistics_all.