Overblog
Editer l'article Suivre ce blog Administration + Créer mon blog
11 octobre 2008 6 11 /10 /octobre /2008 23:04
Bonsoir,

Que tous ceux qui pratiquent les bases de données et qui n'ont jamais eu des problèmes de performances se lèvent.
Je pense que rien qu'avec cette partie, j'aurai de quoi publier pour quelques temps !
Mais comme, il faut commencer par quelque part, j'ai décidé ce soir de m'attaquer à mes amis developpeurs à qui il arrive parfois d'écrire des requêtes que l'on qualifiera de non perforamante.
Mais est-ce vraiment leur faute ! La pluspart du temps, ils viennent du monde C, Java, Dot NET, ou que sais-je encore, et n'ont une experience du sql et d'ORACLE que très brève (voir inexistante).
Il ne faut donc pas s'étonner plus que ca d'avoir des applications "lente" ou inexploitable en accès conccurents.

Pour argumenter cela, j'ai décidé de travailler avec un exemple simple:
Une table facture (id_facture, mois_facture) et une table ligne_facture (id_ligne_facture,id_facture,montant_facture).

Je vais remplir ces tables de facon conséquente. (150 000 factures par mois avec 4 lignes de facture par facture et par mois).

CE qui nous faite 150 000 lignes dans la table facture et 600 000 * 12 =  7 200 000 de lignes !

Ci-dessous un petit script pour faire tout ca:
 

CREATE TABLE FACTURE (ID_FACTURE NUMBER,MOIS_FACTURE NUMBER);
CREATE TABLE LIGNE_FACTURE (ID_LIGNE_FACTURE NUMBER,ID_FACTURE NUMBER, MONTANT_FACTURE FLOAT);
CREATE SEQUENCE NEXtKEY START WITH 1 INCREMENT BY 1 CACHE 100;

ALTER TABLE FACTURE ADD CONSTRAINT PK_FACTURE PRIMARY KEY(Id_Facture);
ALTER TABLE LIGNE_FACTURE ADD CONSTRAINT PK_LIGNE_FACTURE PRIMARY KEY(Id_Ligne_Facture);

/* Remplissage table facture*/

BEGIN
FOR mois IN 1..12 LOOP
   FOR i IN 1..150000 LOOP
      INSERT INTO FACTURE VALUES (NEXTKEY.NEXTVAL,mois);
   END LOOP;
END LOOP;
END;
/

COMMIT;

/* Remplissage de ligne_facture */

BEGIN
   FOR fac in (SELECT ID_FACTURE FROM FACTURE) LOOP
      FOR nb_fac IN 1..4 LOOP
         INSERT INTO LIGNE_FACTURE VALUES (NEXTKEY.NEXTVAL,fac.ID_FACTURE,100*nb_fac);
      END LOOP;
   END LOOP;
END;
/

COMMIT;


CREATE INDEX IDX_1 ON FACTURE (MOIS);
CREATE INDEX IDX_2 ON LIGNE_FaCTURE(ID_FACTURE);


EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,TabName=>'FACTURE',CASCADE=>TRUE);
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,TabName=>'LIGNE_FACTURE',CASCADE=>TRUE);




Maintenant que nos tables sont bien remplies, nous voulons le montant total par mois.
Rien d'extraordinaire à écrire.
Pour info:
Version ORACLE :10.2.0.1
SGA :512 M
PGA :10 M (Faut pas pousser, en tant que dev, j'ai autre chose à faire avec ma mémoire).


SELECT 
Mois_Facture,SUM(Montant_Facture)
FROM FACTURE,LIGNE_FACTURE
WHERE FACTURE.Id_Facture=LIGNE_FACTURE.Id_Facture
GROUP BY Mois_Facture
ORDER BY Mois_Facture;


Avant de lancer ma requete dans SQL +
SET TIMING ON
puis je lance la requete.
Résultat : 30 secondes (je l'ai lancé plusieurs fois) 


Ce résultat me convient moyennement. ORACLE permettant differents type de jointure, je décide de forcer une jointure de type NESTED LOOP. Pour cela, j'utilise  un hint qui indiquera au moteur ORACLE quel type de jointure prendre.(J'ai vu ca sur GOOGLE, parait que ca marche !)


SELECT /*+ USE_NL(FACTURE,LIGNE_FACTURE) */ Mois_Facture,SUM(Montant_Facture)
FROM FACTURE,LIGNE_FACTURE
WHERE FACTURE.Id_Facture=LIGNE_FACTURE.Id_Facture
GROUP BY Mois_Facture
ORDER BY Mois_Facture;


Résultat : 1ere fois 30 secondes, mais la seconde fois 10 secondes !!!
Génial, j'ai donc optimisé ma requete ! je laisse le hint et j'envoi l'ensemble en production !
 


C'est un peu caricatural, mais en cherchant au tour de soi, on peut trouver ce type de situation.


Ce qui est génant dans cette démarche, c'est qu'a aucun moment je n'ai parlé de plan d'execution ou statistiques sur l'execution de ma requete, alors que juste avec SQL PLUS on peut avoir un nombre d'informations assez sympa.


Pour cela, il suffit de taper SET AUTOTRACE ON
En plus du résultat de la requete, j'obtiens également son plan d'execution(qui nous montre bien que l'on effecture une NESTED LOOP pour la jointure) ainsi que certaines statistiques.  
 










Or on constate que le nombre de "consistents gets" est énorme (plus de 3.5 millions).

Je décide donc d'enlever le hint ( /*+ USE_NL(FACTURE,LIGNE_FACTURE) */ ) pour voir ce qui se passe.
Je l'execute 2,3 fois (je suis toujours entre 25 & 30 secondes)
Mais ce qui est interessant c'est que je constate au niveau des statistiques que je n'ai que 23452 consistents gets. En revanche, j'ai un peu plus de 18886 accès disques ! 


    








Ce qui doit expliquer la différence de temps d'execution entre les deux requêtes.
 

En regardant le plan d'execution, je constate également que la jointure est maintenant de type HASH JOIN.
Et la tout s'explique, car ce type de jointure est plus gourmand en PGA (pour la création de table de haschage).

PGA, vous vous souvenez, c'est la zone mémoire que j'ai réduite à son minimum pour faire des économie de mémoire sur mon pc !

Je décide donc d'augmenter ce paramètre system d'ORACLE.
J'ouvre une session SQL PLUS en system et je modifie la valeur.

ALTER SYSTEM SET PGA_AGGREGATE_TARGET=256M;

Et la Oh grande magie de l'informatique, ma requete ne prends plus que 3.6 secondes pour un peu moins de 23 000 consistents gets pour 0 lecture physiques.
    

Si des le départ de notre investigation nous avions utlisé cette option (SET AUTOTRACE ON), nous aurrions pu nous épargner des tentatives d'optimisation pas forcement judicieuces.
 
J'en profite pour rappeler que d'après un postulat admis par un grand nombre, 80 % des problèmes de performance sont liés à des requêtes mal écrite.


LAO.


Partager cet article
Repost0

commentaires

F
Salut,<br /> <br /> Très intéressant. Je suis d'accord sur l'idée que tous les développeurs ne savent pas décrypter un plan d'exécution, sachant que beaucoup des notions sous-jacentes ne sont pas très claires. Par exemple, ça consiste en quoi un "consitent get", et le "recursive calls" ? C'est mal d'en avoir pleins ?<br /> <br /> Ca mériterait presque un tuto avec des cas concret d'interprétation comme tu viens de le faire pour permettre à nos amis développeurs d'y voir plus clair.
Répondre
L
<br /> Salut,<br /> <br /> Effectivement, il faudrait plus d'un article pour parler du sujet. Mais je vais commencer par tenter de répondre à tes deux quesions:<br /> - Recursives calls : C'est lorsqu'une instruction SQL d'une facon ou une autre appelle d'autres requetes SQL.<br /> <br /> Un cas courant est l'utilisation abusive des "user function" qui appellent elles même d'autres fonctions ou procédures. Au final, en essayant de comprendre (via AUTOTRACE) une requête on se<br /> rend compte que finalement ce sont peut etre des dizaines ou des centaines (voir des milliers) de requêtes qui sont executées.<br /> <br /> Un autre cas, c'est par exemple lors d'une insertion: Si il n'y a pas assez de place au niveau ligne, Oracle va allouer dynamiquement de l'espace.<br /> <br /> <br /> <br /> - Consistents gets : Il s'agit en fait de lectures logiques qui s'opposent aux lectureq physiques plus souvent appelées "IO" ou<br /> lectures disques. En fonction de la mémoire allouée (SGA) Oracle conserve en mémoire les données. Lorsque l'on fait un select, Oracle commence par regarder si les données sont en mémoire et dans<br /> ce cas fait un "consitents gets" et si il ne trouve pas l'information, va les chercher sur  disque (physical read). Il va de soit qu'aller chercher les informations en mémoire est bien moins<br /> consommateur que les accès disques. Il n'en demeure pas moins que moins on fait d'accès en mémoire (LIO) mieux on se porte. C'est pas forcement "mal" d'en avoir pleins (tout dépends ce qu'on<br /> ramène), mais si il existe un moyen d'en faire moins, ... alors c'est mieux.<br /> <br /> LAO. <br />  <br /> <br /> <br />
S
Généralement, les développeurs ne sont en effet pas sensibles aux problèmes de perfs. Très rares sont ceux qui savent ce qu'est un hint ou un plan d'exécution. La plupart développent et recettent sur une volumétrie bien plus faible que celle de la production réelle, les temps de réponse peuvent donc être raisonnables malgré des requêtes mal écrites, et ce n'est qu'en production que les vrais problèmes apparaissent ... :-(
Répondre
L
<br /> Bonjour,<br /> Ca sent le vécu ! En tout ca cas, c'est un mal récurrent qui me fait dire que les DBA ont de l'avenir ...<br /> LAO.<br /> <br /> <br />
G
Salut LAO,<br /> <br /> Très bon article, mais qui part du postulat que les développeurs savent interpréter un plan d'exécution, chose qui est malheureusement trop pointue la plupart du temps.<br /> Dans l'exemple :<br /> 1) y a-t-il vraiment beaucoup de développeurs qui auraient placé un HINT ?<br /> 2) dans le cas contraire et pour trouver la deuxième solution, cela tente à prouver l'intérêt d'un DBA de Dev... ;-)
Répondre
L
<br /> salut GIGOT,<br /> <br /> Effectivement, je pars du postulat que des personnes recrtuées pour travailler sur des applications orientées base de données maitrisent ce qu'elles font ou qu'au moins elles soient formées pour le<br /> faire.<br /> Il est evident qu'en fonction des applications, des volumétries, et accès simultanés, cette connaissance peut être plus ou moins aboutie. Il ne me viendrait pas à l'idée de faire construire une<br /> maison par des maçons n'ayant ne sachant pas lire et interpreter un niveau.<br /> Dans la mesure ou dans bon nombre de cas, il n'existe pas de spécification en terme de temps de réponse, cela n'incite pas les développeurs à optimiser leurs traitements. On préfère souvent les<br /> qualificatifs du type "C'est lent, faudrait que ce soit plus rapide"...<br /> <br /> LAO<br /> <br /> <br />
A
Très intéressant ce cas d'école !<br /> <br /> Arnaud
Répondre