Il y'a un peu plus de 2 ans déjà j'avais rédigé un article décrivant le principe des statistiques étendues en 11g.
J'avais tenté d'expliquer comment les stats étendues pouvaient aider le CBO à estimer de meilleures cardinalités lorsqu'on avait des colonnes corrélées ou des expressions dans nos prédicats.
Ces derniers jours j'ai justement eu affaire à 2 problèmes de performances (sur des bases 11g) liés à l'absence de stats sur des colonnes corrélées et des fonctions appliquées à certaines colonnes.
Je me suis donc dit que ces 2 cas réels pouvaient constituer un second article permettant d'illustrer l'article que j'avais écrit.
Cas 1: Statistiques étendues sur une expression
Le premier problème concernait la requête suivante:
D'après le chapitre 5 du livre de Jonathan Lewis, l'optimiseur appliquerait une sélectivité de 5% pour chaque prédicat (avec expression) de type NOT EQUAL.
Vu qu'on a 4 prédicats on se retrouve au final avec une sélectivité de 0.05*0.05*0.05*0.05 = 0.00000625
La colonne NUM_ROWS de DBA_TABLES pour la table TDO_D_ASSIN_ROLE_COORD_BQE renvoyant 18 449 114 lignes, si on y applique la sélectivité précédente on obtient bien 115 lignes:
18449114*0.00000625 = 115.30
Le second problème concernait la requête suivante qui mettait 2h34 pour s'exécuter:
Si on regarde le plan on voit que sur les 2h34 d'exécution on a 2h30 passées sur l'accès à la table ODD_INFO_ADHESION via l'opération "PARTITION RANGE AND".
Cette opération est exécutée 7949000 fois (cf. colonne STARTS) car la jointure précédente entre les tables ODD_FLUX_CID et ODD_ASSURE retourne 7949000 lignes (cf. opération 4 du plan).
Si on regarde la colonne E-ROWS on voit que l'optimiseur estime que la jointure ne retournerait que 7504 lignes. C'est cette mauvaise estimation qui induit derrière le NESTED LOOP hyper couteux sur la table ODD_INFO_ADHESION.
La jointure avec la table ODD_ASSURE s'effectue sur les colonnes NUM_INTEGRATION, ASS_ID_FLUX et ASS_ID_DELEGATAIRE.
Effectuons un comptage sur la table ODD_ASSURE juste avec la clause NUM_INTEGRATION:
On voit que le nombre de lignes retournées est toujours de 7949K alors que cette fois on a utilisé uniqument la colonne NUM_INTEGRATION.
Il semble que les 2 autres colonnes n'influent pas sur la cardinalité. Cela revient donc à dire qu'il existe une corrélation entre les 3 colonnes NUM_INTEGRATION, ASS_ID_FLUX et ASS_ID_DELEGATAIRE.
Il serait donc intéressant de calculer des stats étendues pour la table ODD_ASSURE sur ces 3 colonnes ainsi que pour la table ODD_FLUX_CID:
Si on relance la requête on obtient désormais le plan suivant:
Les stats étendues ont permis d'obtenir une cardinalité pas tout à fait exact mais en tout cas plus proche de la réalité.
Les NESTED LOOP ont laissé place à des HASH JOIN ce qui permet de faire tourner la requête en 1 minute 35 au lieu de 2h34.
J'espère que ces 2 exemples tirés de la réalité vous permettront de comprendre comment grâce aux statistiques étendues on peut aider l'optimiseur à avoir une connaissance plus intelligente des données et ainsi lui permettre de nous trouver un plan adequat.
J'avais tenté d'expliquer comment les stats étendues pouvaient aider le CBO à estimer de meilleures cardinalités lorsqu'on avait des colonnes corrélées ou des expressions dans nos prédicats.
Ces derniers jours j'ai justement eu affaire à 2 problèmes de performances (sur des bases 11g) liés à l'absence de stats sur des colonnes corrélées et des fonctions appliquées à certaines colonnes.
Je me suis donc dit que ces 2 cas réels pouvaient constituer un second article permettant d'illustrer l'article que j'avais écrit.
Cas 1: Statistiques étendues sur une expression
Le premier problème concernait la requête suivante:
SELECT to_char(SYSDATE, 'YYYYMMDD') AS DATE_EXTRACTION, RBQ.RBQ_NUM_CONTR AS CPP_NUM_CONTR, RBQ.RBQ_NUM_CONTR, CB.CBQ_CD_ETABLISSEMENT, CB.CBQ_CD_GUICHET, CB.CBQ_CLE_COMPTE, RBQ.RBQ_CD_NAT_COORD_BQE, Substr(CB.CBQ_NUM_COMPTE,1,7) AS CBQ_NUM_COMPTE1, Substr(CB.CBQ_NUM_COMPTE,8,3) AS CBQ_NUM_COMPTE2, Substr(CB.CBQ_NUM_COMPTE,11,1) AS CBQ_NUM_COMPTE3 FROM TDO_D_ASSIN_COORD_BQE CB, TDO_D_ASSIN_ROLE_COORD_BQE RBQ, TDO_D_ASSIN_CONTR_ASSU CON, TDO_R_PDV_DISTRIB PDVD WHERE RBQ.RBQ_NUM_CONTR in (select NUM_CONTRAT from GIC_DEM_LISTE_CONTRATS ) AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '398' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '399' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '400' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '412' -- AND CB.CBQ_ID_PERSONNE = RBQ.RBQ_ID_PERSONNE AND CB.CBQ_ID_TEC_COORD_BQE = RBQ.RBQ_ID_TEC_COORD_BQE -- AND CON_NUM_CONTR(+) = RBQ.RBQ_NUM_CONTR AND ( CON.CON_CD_PVE_SOUS IS NULL OR ( PDVD_CD_POINT_VENTE = CON.CON_CD_PVE_SOUS AND PDVD_CD_DISTRIB = '400000' ) );
Cette requête existait depuis longtemps mais, suite à l'implémentation du partitioning sur certaines tables, elle s'était mise à tourner pendant des dizaines d'heures.
Même si la clause de partitioning n'était effectivement pas indiquée dans la clause WHERE de la requête (et ça c'est pas bien!!!) le client voulait quand même que cette requête puisse s'exécuter correctement en attendant que la correction soit apportée.
Dans la requête on peut noter les expressions suivantes:
Même si la clause de partitioning n'était effectivement pas indiquée dans la clause WHERE de la requête (et ça c'est pas bien!!!) le client voulait quand même que cette requête puisse s'exécuter correctement en attendant que la correction soit apportée.
Dans la requête on peut noter les expressions suivantes:
substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '398' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '399' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '400' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '412'
En fait le critère de partitionnement est un champ qui correspond justement aux 3 premiers caractères du champ RBQ_NUM_CONTR manipulé dans les clauses ci-dessus.
Jetons un œil au plan d’exécution de la requête(la requête n'ayant pas pu aboutir j'ai uniquement le plan sans les stats d’exécutions):
Jetons un œil au plan d’exécution de la requête(la requête n'ayant pas pu aboutir j'ai uniquement le plan sans les stats d’exécutions):
---------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | ---------------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 89 | 12460 | 31322 (8)| 00:01:50 | | | | 1 | NESTED LOOPS | | 89 | 12460 | 31322 (8)| 00:01:50 | | | | 2 | NESTED LOOPS | | 89 | 12460 | 31322 (8)| 00:01:50 | | | |* 3 | HASH JOIN | | 89 | 7565 | 31056 (8)| 00:01:49 | | | | 4 | NESTED LOOPS | | 89 | 6675 | 31033 (8)| 00:01:49 | | | | 5 | NESTED LOOPS OUTER | | 115 | 7015 | 26689 (8)| 00:01:34 | | | | 6 | PARTITION LIST ALL | | 115 | 5060 | 26459 (8)| 00:01:33 | 1 | 227 | |* 7 | TABLE ACCESS FULL | TDO_D_ASSIN_ROLE_COORD_BQE | 115 | 5060 | 26459 (8)| 00:01:33 | 1 | 227 | | 8 | TABLE ACCESS BY GLOBAL INDEX ROWID| TDO_D_ASSIN_CONTR_ASSU | 1 | 17 | 2 (0)| 00:00:01 | ROWID | ROWID | |* 9 | INDEX UNIQUE SCAN | PK_ASSIN_CON_NUM_CONTR | 1 | | 1 (0)| 00:00:01 | | | |* 10 | INDEX FAST FULL SCAN | UK_TDO_R_PDV_DISTRIB | 1 | 14 | 38 (6)| 00:00:01 | | | | 11 | INDEX FAST FULL SCAN | PK_GIC_DEM_LISTE_CONTRATS | 20000 | 195K| 22 (5)| 00:00:01 | | | |* 12 | INDEX RANGE SCAN | PK_ASSIN_CBQ_TEC_COORD_PERSO | 1 | | 2 (0)| 00:00:01 | | | | 13 | TABLE ACCESS BY INDEX ROWID | TDO_D_ASSIN_COORD_BQE | 1 | 55 | 3 (0)| 00:00:01 | | | ---------------------------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("RBQ"."RBQ_NUM_CONTR"="NUM_CONTRAT") 7 - filter(SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'399' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'400' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'412') 9 - access("CON_NUM_CONTR"(+)="RBQ"."RBQ_NUM_CONTR") 10 - filter("CON"."CON_CD_PVE_SOUS" IS NULL OR "PDVD_CD_POINT_VENTE"="CON"."CON_CD_PVE_SOUS" AND "PDVD_CD_DISTRIB"='400000') 12 - access("CB"."CBQ_ID_PERSONNE"="RBQ"."RBQ_ID_PERSONNE" AND "CB"."CBQ_ID_TEC_COORD_BQE"="RBQ"."RBQ_ID_TEC_COORD_BQE")
On voit que le CBO a choisi d'attaquer en premier lieu la table TDO_D_ASSIN_ROLE_COORD_BQE en estimant que celle-ci retournerait (après avoir appliqué les critères avec la fonction SUBSTR) seulement 115 lignes .
Si on effectue un comptage sur cette table on se rend compte que le nombre de lignes retournées est en réalité de 18 millions:
Puisque le CBO est incapable d'estimer une sélectivité correcte lorsqu'une fonction est appliquée à une colonne, d'où diable sort-il ces 115 lignes?Si on effectue un comptage sur cette table on se rend compte que le nombre de lignes retournées est en réalité de 18 millions:
select count(*) from TDO_D_ASSIN_ROLE_COORD_BQE RBQ where substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '398' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '399' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '400' AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '412'; COUNT(*) ---------- 18449114 ------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | ------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 |00:00:48.70 | 31949 | 31825 | | 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:48.70 | 31949 | 31825 | |* 2 | INDEX FAST FULL SCAN| CK_ASSIN_RBQ_NUM_CONTR | 1 | 115 | 18M|00:00:44.48 | 31949 | 31825 | ------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter((SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'399' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'400' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'412'))
D'après le chapitre 5 du livre de Jonathan Lewis, l'optimiseur appliquerait une sélectivité de 5% pour chaque prédicat (avec expression) de type NOT EQUAL.
Vu qu'on a 4 prédicats on se retrouve au final avec une sélectivité de 0.05*0.05*0.05*0.05 = 0.00000625
La colonne NUM_ROWS de DBA_TABLES pour la table TDO_D_ASSIN_ROLE_COORD_BQE renvoyant 18 449 114 lignes, si on y applique la sélectivité précédente on obtient bien 115 lignes:
18449114*0.00000625 = 115.30
Cette mauvaise estimation conduit le CBO à choisir cette table comme table directrice du NESTED LOOP pour la joindre avec la table TDO_D_ASSIN_CONTR_ASSU.
Les estimations étant totalement biaisées c'est tout le reste du plan qui est faussé.
La solution en 11g consiste à calculer des statistiques étendues sur l'expression substr(RBQ.RBQ_NUM_CONTR, 1, 3):
Les estimations étant totalement biaisées c'est tout le reste du plan qui est faussé.
La solution en 11g consiste à calculer des statistiques étendues sur l'expression substr(RBQ.RBQ_NUM_CONTR, 1, 3):
BEGIN DBMS_STATS.gather_table_stats (ownname => 'ADWH00', tabname => 'TDO_D_ASSIN_ROLE_COORD_BQE', method_opt => 'FOR COLUMNS (substr(RBQ_NUM_CONTR, 1, 3)) size 1' ); END; /
J'ai mis le paramètre d'instance ENABLE_DDL_LOGGING à TRUE et voici ce qu'on voit dans le fichier ALERT lorsqu'on calcule des stats étendues sur l'expression substr(RBQ_NUM_CONTR, 1, 3):
Le calcul de stats étendues sur l'expression génère en réalité la création d'une colonne virtuelle sur la table TDO_D_ASSIN_ROLE_COORD_BQE.
Une fois les stats étendues calculées (c-a-d une fois la colonne virtuelle créée), on obtient le plan suivant lorsqu'on exécute de nouveau la requête:
Tout d'abord on note que la requête s'exécute en 3 minutes (alors qu'elle n'aboutissait pas sans les stats étendues). Ensuite on voit que la cardinalité est bien estimée pour la table TDO_D_ASSIN_ROLE_COORD_BQE (18M pour les colonnes E-ROWS et A-ROWS).
Enfin, on constate que grâce à cette bonne estimation la table n'est plus attaquée via un NESTED LOOP mais que le CBO a opté pour un HASH JOIN tout à fait justifié.
alter table "ADWH00"."TDO_D_ASSIN_ROLE_COORD_BQE" add (SYS_STUXQ$Y7DH65G7U2BC3$3Y_3NF as (substr(RBQ_NUM_CONTR, 1, 3)) virtual BY USER for statistics)
Le calcul de stats étendues sur l'expression génère en réalité la création d'une colonne virtuelle sur la table TDO_D_ASSIN_ROLE_COORD_BQE.
Une fois les stats étendues calculées (c-a-d une fois la colonne virtuelle créée), on obtient le plan suivant lorsqu'on exécute de nouveau la requête:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 18229 |00:03:09.28 | 429K| 311K| | | | | 1 | CONCATENATION | | 1 | | 18229 |00:03:09.28 | 429K| 311K| | | | |* 2 | HASH JOIN | | 1 | 20717 | 18229 |00:02:03.16 | 288K| 228K| 2494K| 1736K| 2540K (0)| |* 3 | INDEX FAST FULL SCAN | UK_TDO_R_PDV_DISTRIB | 1 | 28501 | 28497 |00:00:00.02 | 167 | 0 | | | | | 4 | NESTED LOOPS OUTER | | 1 | 20717 | 18229 |00:02:03.07 | 288K| 228K| | | | |* 5 | HASH JOIN | | 1 | 20717 | 18229 |00:02:02.62 | 232K| 228K| 2327K| 1129K| 3297K (0)| |* 6 | HASH JOIN | | 1 | 20717 | 18261 |00:01:22.93 | 86307 | 83282 | 2129K| 2004K| 2289K (0)| | 7 | INDEX FAST FULL SCAN | PK_GIC_DEM_LISTE_CONTRATS | 1 | 20000 | 20000 |00:00:00.02 | 2718 | 0 | | | | | 8 | PARTITION LIST ALL | | 1 | 18M| 18M|00:01:09.89 | 83589 | 83282 | | | | |* 9 | TABLE ACCESS FULL | TDO_D_ASSIN_ROLE_COORD_BQE | 227 | 18M| 18M|00:01:02.25 | 83589 | 83282 | | | | | 10 | TABLE ACCESS FULL | TDO_D_ASSIN_COORD_BQE | 1 | 16M| 16M|00:00:27.26 | 146K| 145K| | | | | 11 | TABLE ACCESS BY GLOBAL INDEX ROWID | TDO_D_ASSIN_CONTR_ASSU | 18229 | 1 | 18229 |00:00:00.41 | 55907 | 0 | | | | |* 12 | INDEX UNIQUE SCAN | PK_ASSIN_CON_NUM_CONTR | 18229 | 1 | 18229 |00:00:00.23 | 37677 | 0 | | | | | 13 | NESTED LOOPS | | 1 | 1 | 0 |00:01:06.09 | 141K| 83282 | | | | | 14 | NESTED LOOPS | | 1 | 1 | 0 |00:01:06.09 | 141K| 83282 | | | | | 15 | NESTED LOOPS | | 1 | 1 | 0 |00:01:06.09 | 141K| 83282 | | | | |* 16 | FILTER | | 1 | | 0 |00:01:06.09 | 141K| 83282 | | | | | 17 | NESTED LOOPS OUTER | | 1 | 1 | 18261 |00:01:06.08 | 141K| 83282 | | | | |* 18 | HASH JOIN | | 1 | 20717 | 18261 |00:01:05.80 | 86306 | 83282 | 2129K| 2004K| 2283K (0)| | 19 | INDEX FAST FULL SCAN | PK_GIC_DEM_LISTE_CONTRATS | 1 | 20000 | 20000 |00:00:00.02 | 2718 | 0 | | | | | 20 | PARTITION LIST ALL | | 1 | 18M| 18M|00:00:52.92 | 83588 | 83282 | | | | |* 21 | TABLE ACCESS FULL | TDO_D_ASSIN_ROLE_COORD_BQE | 227 | 18M| 18M|00:00:45.32 | 83588 | 83282 | | | | | 22 | TABLE ACCESS BY GLOBAL INDEX ROWID| TDO_D_ASSIN_CONTR_ASSU | 18261 | 1 | 18261 |00:00:00.26 | 54798 | 0 | | | | |* 23 | INDEX UNIQUE SCAN | PK_ASSIN_CON_NUM_CONTR | 18261 | 1 | 18261 |00:00:00.13 | 36537 | 0 | | | | |* 24 | INDEX FAST FULL SCAN | UK_TDO_R_PDV_DISTRIB | 0 | 36746 | 0 |00:00:00.01 | 0 | 0 | | | | |* 25 | INDEX RANGE SCAN | PK_ASSIN_CBQ_TEC_COORD_PERSO | 0 | 1 | 0 |00:00:00.01 | 0 | 0 | | | | | 26 | TABLE ACCESS BY INDEX ROWID | TDO_D_ASSIN_COORD_BQE | 0 | 1 | 0 |00:00:00.01 | 0 | 0 | | | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("PDVD_CD_POINT_VENTE"="CON"."CON_CD_PVE_SOUS") 3 - filter("PDVD_CD_DISTRIB"='400000') 5 - access("CB"."CBQ_ID_PERSONNE"="RBQ"."RBQ_ID_PERSONNE" AND "CB"."CBQ_ID_TEC_COORD_BQE"="RBQ"."RBQ_ID_TEC_COORD_BQE") 6 - access("RBQ"."RBQ_NUM_CONTR"="NUM_CONTRAT") 9 - filter((SUBSTR("RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'399' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'400' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'412')) 12 - access("CON_NUM_CONTR"="RBQ"."RBQ_NUM_CONTR") 16 - filter("CON"."CON_CD_PVE_SOUS" IS NULL) 18 - access("RBQ"."RBQ_NUM_CONTR"="NUM_CONTRAT") 21 - filter((SUBSTR("RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'399' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'400' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'412')) 23 - access("CON_NUM_CONTR"="RBQ"."RBQ_NUM_CONTR") 24 - filter((LNNVL("PDVD_CD_POINT_VENTE"="CON"."CON_CD_PVE_SOUS") OR LNNVL("PDVD_CD_DISTRIB"='400000'))) 25 - access("CB"."CBQ_ID_PERSONNE"="RBQ"."RBQ_ID_PERSONNE" AND "CB"."CBQ_ID_TEC_COORD_BQE"="RBQ"."RBQ_ID_TEC_COORD_BQE")
Tout d'abord on note que la requête s'exécute en 3 minutes (alors qu'elle n'aboutissait pas sans les stats étendues). Ensuite on voit que la cardinalité est bien estimée pour la table TDO_D_ASSIN_ROLE_COORD_BQE (18M pour les colonnes E-ROWS et A-ROWS).
Enfin, on constate que grâce à cette bonne estimation la table n'est plus attaquée via un NESTED LOOP mais que le CBO a opté pour un HASH JOIN tout à fait justifié.
Cas 2: Stats étendues sur des colonnes corrélées
SELECT DISTINCT FLC_DT_FIN_ECH_FLUX Date_arrete, ASS_NUM_CONTR_COLLECT_CNP Num_contrat, ASS_NUM_CONTRACTANT Id_coll, SUM ( DECODE (ASS_NUM_CONTRACTANT, '00698', 12 * ADH_SOMME_MVT_RELATIFS_TTC, '77009', 12 * ADH_SOMME_MVT_RELATIFS_TTC, '90074', 12 * ADH_SOMME_MVT_RELATIFS_TTC, 4 * ADH_SOMME_MVT_RELATIFS_TTC)) FROM odd_flux_cid a, odd_assure b, odd_info_adhesion d where a.num_integration in (8118,8112,8069,8186,8148,8119,8094,8070,8187,8149,8120,8095,8071,8121,8096,8072,8188,8150,8122,8097,8073,8189,8151,8123,8098,8074,8190,8152,8125,8099,8075,8191,8153,8127, 8100,8192,8154,8129,8101,8077,8193,8155,8130,8102,8078,8194,8156,8131,8103,8079,8195,8158,8132,8104,8080,8197,8159,8133,8105,8081,8162,8134,8106,8082,8198,8163,8135,8107,8083,8200,8164,8136,8108,8084, 8137,8110,8085,8165,8138,8109,8086,8202,8166,8139,8111,8087) AND b.num_integration = d.num_integration AND ASS_ID_FLUX = ADH_ID_FLUX AND ASS_NUM_CCOLTE = ADH_NUM_CCOLTE AND ASS_NUM_REF_ASS = ADH_NUM_REF_ASS AND a.num_integration = b.num_integration AND ASS_ID_FLUX = flc_id_flux AND ASS_ID_DELEGATAIRE = flc_id_delegataire GROUP BY FLC_DT_FIN_ECH_FLUX, ASS_NUM_CONTR_COLLECT_CNP, ASS_NUM_CONTRACTANT; 1469 rows selected. Elapsed: 02:34:56.28 Plan hash value: 863059560 ------------------------------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1469 |00:11:35.38 | 13M| 392K| | | | | 1 | HASH GROUP BY | | 1 | 11546 | 1469 |00:11:35.38 | 13M| 392K| 855K| 855K| 2582K (0)| | 2 | NESTED LOOPS | | 1 | | 11M|02:34:29.63 | 13M| 392K| | | | | 3 | NESTED LOOPS | | 1 | 11546 | 11M|02:32:29.42 | 2327K| 258K| | | | | 4 | NESTED LOOPS | | 1 | 7504 | 7949K|00:01:24.63 | 1442K| 184K| | | | | 5 | PARTITION RANGE INLIST | | 1 | 1310 | 86 |00:00:01.02 | 173 | 172 | | | | |* 6 | TABLE ACCESS FULL | ODD_FLUX_CID | 86 | 1310 | 86 |00:00:01.02 | 173 | 172 | | | | | 7 | PARTITION RANGE AND | | 86 | 6 | 7949K|00:01:20.55 | 1442K| 184K| | | | |* 8 | TABLE ACCESS BY LOCAL INDEX ROWID| ODD_ASSURE | 86 | 6 | 7949K|00:01:17.40 | 1442K| 184K| | | | |* 9 | INDEX RANGE SCAN | PK_ASS_ASSURE | 86 | 56 | 7949K|00:00:17.86 | 34929 | 34746 | | | | | 10 | PARTITION RANGE AND | | 7949K| 1 | 11M|02:30:54.44 | 884K| 74033 | | | | |* 11 | INDEX RANGE SCAN | PK_ADH_INFO_ADHESION | 7949K| 1 | 11M|00:01:04.85 | 884K| 74033 | | | | | 12 | TABLE ACCESS BY LOCAL INDEX ROWID | ODD_INFO_ADHESION | 11M| 2 | 11M|00:01:46.81 | 11M| 134K| | | | ------------------------------------------------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 6 - filter(("A"."NUM_INTEGRATION"=8069 OR "A"."NUM_INTEGRATION"=8070 OR "A"."NUM_INTEGRATION"=8071 OR "A"."NUM_INTEGRATION"=8072 OR "A"."NUM_INTEGRATION"=8073 OR "A"."NUM_INTEGRATION"=8074 OR "A"."NUM_INTEGRATION"=8075 OR "A"."NUM_INTEGRATION"=8077 OR "A"."NUM_INTEGRATION"=8078 OR "A"."NUM_INTEGRATION"=8079 OR "A"."NUM_INTEGRATION"=8080 OR "A"."NUM_INTEGRATION"=8081 OR "A"."NUM_INTEGRATION"=8082 OR "A"."NUM_INTEGRATION"=8083 OR "A"."NUM_INTEGRATION"=8084 OR "A"."NUM_INTEGRATION"=8085 OR "A"."NUM_INTEGRATION"=8086 OR "A"."NUM_INTEGRATION"=8087 OR "A"."NUM_INTEGRATION"=8094 OR "A"."NUM_INTEGRATION"=8095 OR "A"."NUM_INTEGRATION"=8096 OR "A"."NUM_INTEGRATION"=8097 OR "A"."NUM_INTEGRATION"=8098 OR "A"."NUM_INTEGRATION"=8099 OR "A"."NUM_INTEGRATION"=8100 OR "A"."NUM_INTEGRATION"=8101 OR "A"."NUM_INTEGRATION"=8102 OR "A"."NUM_INTEGRATION"=8103 OR "A"."NUM_INTEGRATION"=8104 OR "A"."NUM_INTEGRATION"=8105 OR "A"."NUM_INTEGRATION"=8106 OR "A"."NUM_INTEGRATION"=8107 OR "A"."NUM_INTEGRATION"=8108 OR "A"."NUM_INTEGRATION"=8109 OR "A"."NUM_INTEGRATION"=8110 OR "A"."NUM_INTEGRATION"=8111 OR "A"."NUM_INTEGRATION"=8112 OR "A"."NUM_INTEGRATION"=8118 OR "A"."NUM_INTEGRATION"=8119 OR "A"."NUM_INTEGRATION"=8120 OR "A"."NUM_INTEGRATION"=8121 OR "A"."NUM_INTEGRATION"=8122 OR "A"."NUM_INTEGRATION"=8123 OR "A"."NUM_INTEGRATION"=8125 OR "A"."NUM_INTEGRATION"=8127 OR "A"."NUM_INTEGRATION"=8129 OR "A"."NUM_INTEGRATION"=8130 OR "A"."NUM_INTEGRATION"=8131 OR "A"."NUM_INTEGRATION"=8132 OR "A"."NUM_INTEGRATION"=8133 OR "A"."NUM_INTEGRATION"=8134 OR "A"."NUM_INTEGRATION"=8135 OR "A"."NUM_INTEGRATION"=8136 OR "A"."NUM_INTEGRATION"=8137 OR "A"."NUM_INTEGRATION"=8138 OR "A"."NUM_INTEGRATION"=8139 OR "A"."NUM_INTEGRATION"=8148 OR "A"."NUM_INTEGRATION"=8149 OR "A"."NUM_INTEGRATION"=8150 OR "A"."NUM_INTEGRATION"=8151 OR "A"."NUM_INTEGRATION"=8152 OR "A"."NUM_INTEGRATION"=8153 OR "A"."NUM_INTEGRATION"=8154 OR "A"."NUM_INTEGRATION"=8155 OR "A"."NUM_INTEGRATION"=8156 OR "A"."NUM_INTEGRATION"=8158 OR "A"."NUM_INTEGRATION"=8159 OR "A"."NUM_INTEGRATION"=8162 OR "A"."NUM_INTEGRATION"=8163 OR "A"."NUM_INTEGRATION"=8164 OR "A"."NUM_INTEGRATION"=8165 OR "A"."NUM_INTEGRATION"=8166 OR "A"."NUM_INTEGRATION"=8186 OR "A"."NUM_INTEGRATION"=8187 OR "A"."NUM_INTEGRATION"=8188 OR "A"."NUM_INTEGRATION"=8189 OR "A"."NUM_INTEGRATION"=8190 OR "A"."NUM_INTEGRATION"=8191 OR "A"."NUM_INTEGRATION"=8192 OR "A"."NUM_INTEGRATION"=8193 OR "A"."NUM_INTEGRATION"=8194 OR "A"."NUM_INTEGRATION"=8195 OR "A"."NUM_INTEGRATION"=8197 OR "A"."NUM_INTEGRATION"=8198 OR "A"."NUM_INTEGRATION"=8200 OR "A"."NUM_INTEGRATION"=8202)) 8 - filter("ASS_ID_DELEGATAIRE"="FLC_ID_DELEGATAIRE") 9 - access("A"."NUM_INTEGRATION"="B"."NUM_INTEGRATION" AND "ASS_ID_FLUX"="FLC_ID_FLUX") filter(("B"."NUM_INTEGRATION"=8069 OR "B"."NUM_INTEGRATION"=8070 OR "B"."NUM_INTEGRATION"=8071 OR "B"."NUM_INTEGRATION"=8072 OR "B"."NUM_INTEGRATION"=8073 OR "B"."NUM_INTEGRATION"=8074 OR "B"."NUM_INTEGRATION"=8075 OR "B"."NUM_INTEGRATION"=8077 OR "B"."NUM_INTEGRATION"=8078 OR "B"."NUM_INTEGRATION"=8079 OR "B"."NUM_INTEGRATION"=8080 OR "B"."NUM_INTEGRATION"=8081 OR "B"."NUM_INTEGRATION"=8082 OR "B"."NUM_INTEGRATION"=8083 OR "B"."NUM_INTEGRATION"=8084 OR "B"."NUM_INTEGRATION"=8085 OR "B"."NUM_INTEGRATION"=8086 OR "B"."NUM_INTEGRATION"=8087 OR "B"."NUM_INTEGRATION"=8094 OR "B"."NUM_INTEGRATION"=8095 OR "B"."NUM_INTEGRATION"=8096 OR "B"."NUM_INTEGRATION"=8097 OR "B"."NUM_INTEGRATION"=8098 OR "B"."NUM_INTEGRATION"=8099 OR "B"."NUM_INTEGRATION"=8100 OR "B"."NUM_INTEGRATION"=8101 OR "B"."NUM_INTEGRATION"=8102 OR "B"."NUM_INTEGRATION"=8103 OR "B"."NUM_INTEGRATION"=8104 OR "B"."NUM_INTEGRATION"=8105 OR "B"."NUM_INTEGRATION"=8106 OR "B"."NUM_INTEGRATION"=8107 OR "B"."NUM_INTEGRATION"=8108 OR "B"."NUM_INTEGRATION"=8109 OR "B"."NUM_INTEGRATION"=8110 OR "B"."NUM_INTEGRATION"=8111 OR "B"."NUM_INTEGRATION"=8112 OR "B"."NUM_INTEGRATION"=8118 OR "B"."NUM_INTEGRATION"=8119 OR "B"."NUM_INTEGRATION"=8120 OR "B"."NUM_INTEGRATION"=8121 OR "B"."NUM_INTEGRATION"=8122 OR "B"."NUM_INTEGRATION"=8123 OR "B"."NUM_INTEGRATION"=8125 OR "B"."NUM_INTEGRATION"=8127 OR "B"."NUM_INTEGRATION"=8129 OR "B"."NUM_INTEGRATION"=8130 OR "B"."NUM_INTEGRATION"=8131 OR "B"."NUM_INTEGRATION"=8132 OR "B"."NUM_INTEGRATION"=8133 OR "B"."NUM_INTEGRATION"=8134 OR "B"."NUM_INTEGRATION"=8135 OR "B"."NUM_INTEGRATION"=8136 OR "B"."NUM_INTEGRATION"=8137 OR "B"."NUM_INTEGRATION"=8138 OR "B"."NUM_INTEGRATION"=8139 OR "B"."NUM_INTEGRATION"=8148 OR "B"."NUM_INTEGRATION"=8149 OR "B"."NUM_INTEGRATION"=8150 OR "B"."NUM_INTEGRATION"=8151 OR "B"."NUM_INTEGRATION"=8152 OR "B"."NUM_INTEGRATION"=8153 OR "B"."NUM_INTEGRATION"=8154 OR "B"."NUM_INTEGRATION"=8155 OR "B"."NUM_INTEGRATION"=8156 OR "B"."NUM_INTEGRATION"=8158 OR "B"."NUM_INTEGRATION"=8159 OR "B"."NUM_INTEGRATION"=8162 OR "B"."NUM_INTEGRATION"=8163 OR "B"."NUM_INTEGRATION"=8164 OR "B"."NUM_INTEGRATION"=8165 OR "B"."NUM_INTEGRATION"=8166 OR "B"."NUM_INTEGRATION"=8186 OR "B"."NUM_INTEGRATION"=8187 OR "B"."NUM_INTEGRATION"=8188 OR "B"."NUM_INTEGRATION"=8189 OR "B"."NUM_INTEGRATION"=8190 OR "B"."NUM_INTEGRATION"=8191 OR "B"."NUM_INTEGRATION"=8192 OR "B"."NUM_INTEGRATION"=8193 OR "B"."NUM_INTEGRATION"=8194 OR "B"."NUM_INTEGRATION"=8195 OR "B"."NUM_INTEGRATION"=8197 OR "B"."NUM_INTEGRATION"=8198 OR "B"."NUM_INTEGRATION"=8200 OR "B"."NUM_INTEGRATION"=8202)) 11 - access("B"."NUM_INTEGRATION"="D"."NUM_INTEGRATION" AND "ASS_ID_FLUX"="ADH_ID_FLUX" AND "ASS_NUM_CCOLTE"="ADH_NUM_CCOLTE" AND "ASS_NUM_REF_ASS"="ADH_NUM_REF_ASS") filter(("D"."NUM_INTEGRATION"=8069 OR "D"."NUM_INTEGRATION"=8070 OR "D"."NUM_INTEGRATION"=8071 OR "D"."NUM_INTEGRATION"=8072 OR "D"."NUM_INTEGRATION"=8073 OR "D"."NUM_INTEGRATION"=8074 OR "D"."NUM_INTEGRATION"=8075 OR "D"."NUM_INTEGRATION"=8077 OR "D"."NUM_INTEGRATION"=8078 OR "D"."NUM_INTEGRATION"=8079 OR "D"."NUM_INTEGRATION"=8080 OR "D"."NUM_INTEGRATION"=8081 OR "D"."NUM_INTEGRATION"=8082 OR "D"."NUM_INTEGRATION"=8083 OR "D"."NUM_INTEGRATION"=8084 OR "D"."NUM_INTEGRATION"=8085 OR "D"."NUM_INTEGRATION"=8086 OR "D"."NUM_INTEGRATION"=8087 OR "D"."NUM_INTEGRATION"=8094 OR "D"."NUM_INTEGRATION"=8095 OR "D"."NUM_INTEGRATION"=8096 OR "D"."NUM_INTEGRATION"=8097 OR "D"."NUM_INTEGRATION"=8098 OR "D"."NUM_INTEGRATION"=8099 OR "D"."NUM_INTEGRATION"=8100 OR "D"."NUM_INTEGRATION"=8101 OR "D"."NUM_INTEGRATION"=8102 OR "D"."NUM_INTEGRATION"=8103 OR "D"."NUM_INTEGRATION"=8104 OR "D"."NUM_INTEGRATION"=8105 OR "D"."NUM_INTEGRATION"=8106 OR "D"."NUM_INTEGRATION"=8107 OR "D"."NUM_INTEGRATION"=8108 OR "D"."NUM_INTEGRATION"=8109 OR "D"."NUM_INTEGRATION"=8110 OR "D"."NUM_INTEGRATION"=8111 OR "D"."NUM_INTEGRATION"=8112 OR "D"."NUM_INTEGRATION"=8118 OR "D"."NUM_INTEGRATION"=8119 OR "D"."NUM_INTEGRATION"=8120 OR "D"."NUM_INTEGRATION"=8121 OR "D"."NUM_INTEGRATION"=8122 OR "D"."NUM_INTEGRATION"=8123 OR "D"."NUM_INTEGRATION"=8125 OR "D"."NUM_INTEGRATION"=8127 OR "D"."NUM_INTEGRATION"=8129 OR "D"."NUM_INTEGRATION"=8130 OR "D"."NUM_INTEGRATION"=8131 OR "D"."NUM_INTEGRATION"=8132 OR "D"."NUM_INTEGRATION"=8133 OR "D"."NUM_INTEGRATION"=8134 OR "D"."NUM_INTEGRATION"=8135 OR "D"."NUM_INTEGRATION"=8136 OR "D"."NUM_INTEGRATION"=8137 OR "D"."NUM_INTEGRATION"=8138 OR "D"."NUM_INTEGRATION"=8139 OR "D"."NUM_INTEGRATION"=8148 OR "D"."NUM_INTEGRATION"=8149 OR "D"."NUM_INTEGRATION"=8150 OR "D"."NUM_INTEGRATION"=8151 OR "D"."NUM_INTEGRATION"=8152 OR "D"."NUM_INTEGRATION"=8153 OR "D"."NUM_INTEGRATION"=8154 OR "D"."NUM_INTEGRATION"=8155 OR "D"."NUM_INTEGRATION"=8156 OR "D"."NUM_INTEGRATION"=8158 OR "D"."NUM_INTEGRATION"=8159 OR "D"."NUM_INTEGRATION"=8162 OR "D"."NUM_INTEGRATION"=8163 OR "D"."NUM_INTEGRATION"=8164 OR "D"."NUM_INTEGRATION"=8165 OR "D"."NUM_INTEGRATION"=8166 OR "D"."NUM_INTEGRATION"=8186 OR "D"."NUM_INTEGRATION"=8187 OR "D"."NUM_INTEGRATION"=8188 OR "D"."NUM_INTEGRATION"=8189 OR "D"."NUM_INTEGRATION"=8190 OR "D"."NUM_INTEGRATION"=8191 OR "D"."NUM_INTEGRATION"=8192 OR "D"."NUM_INTEGRATION"=8193 OR "D"."NUM_INTEGRATION"=8194 OR "D"."NUM_INTEGRATION"=8195 OR "D"."NUM_INTEGRATION"=8197 OR "D"."NUM_INTEGRATION"=8198 OR "D"."NUM_INTEGRATION"=8200 OR "D"."NUM_INTEGRATION"=8202))
Cette opération est exécutée 7949000 fois (cf. colonne STARTS) car la jointure précédente entre les tables ODD_FLUX_CID et ODD_ASSURE retourne 7949000 lignes (cf. opération 4 du plan).
Si on regarde la colonne E-ROWS on voit que l'optimiseur estime que la jointure ne retournerait que 7504 lignes. C'est cette mauvaise estimation qui induit derrière le NESTED LOOP hyper couteux sur la table ODD_INFO_ADHESION.
La jointure avec la table ODD_ASSURE s'effectue sur les colonnes NUM_INTEGRATION, ASS_ID_FLUX et ASS_ID_DELEGATAIRE.
Effectuons un comptage sur la table ODD_ASSURE juste avec la clause NUM_INTEGRATION:
select count(*) from ODD_ASSURE b where b.num_integration in (8118,8112,8069,8186,8148,8119,8094,8070,8187,8149,8120,8095,8071,8121,8096,8072,8188,8150,8122,8097,8073,8189,8151,8123,8098,8074,8190,8152,8125,8099,8075,8191,8153,8127, 8100,8192,8154,8129,8101,8077,8193,8155,8130,8102,8078,8194,8156,8131,8103,8079,8195,8158,8132,8104,8080,8197,8159,8133,8105,8081,8162,8134,8106,8082,8198,8163,8135,8107,8083,8200,8164,8136,8108,8084, 8137,8110,8085,8165,8138,8109,8086,8202,8166,8139,8111,8087); --------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | --------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 |00:00:12.96 | 34926 | 34925 | | 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:12.96 | 34926 | 34925 | | 2 | INLIST ITERATOR | | 1 | | 7949K|00:00:11.72 | 34926 | 34925 | | 3 | PARTITION RANGE ITERATOR| | 86 | 7967K| 7949K|00:00:09.01 | 34926 | 34925 | |* 4 | INDEX RANGE SCAN | PK_ASS_ASSURE | 86 | 7967K| 7949K|00:00:06.37 | 34926 | 34925 | ---------------------------------------------------------------------------------------------------------------
Il semble que les 2 autres colonnes n'influent pas sur la cardinalité. Cela revient donc à dire qu'il existe une corrélation entre les 3 colonnes NUM_INTEGRATION, ASS_ID_FLUX et ASS_ID_DELEGATAIRE.
Il serait donc intéressant de calculer des stats étendues pour la table ODD_ASSURE sur ces 3 colonnes ainsi que pour la table ODD_FLUX_CID:
BEGIN DBMS_STATS.gather_table_stats('AODD02','ODD_ASSURE', method_opt => 'FOR COLUMNS (NUM_INTEGRATION,ASS_ID_FLUX,ASS_ID_DELEGATAIRE) size 1', NO_INVALIDATE => FALSE, FORCE => TRUE ); END; / BEGIN DBMS_STATS.gather_table_stats('AODD02','ODD_FLUX_CID', method_opt => 'FOR COLUMNS (NUM_INTEGRATION,FLC_ID_FLUX,FLC_ID_DELEGATAIRE) size 1', NO_INVALIDATE => FALSE, FORCE => TRUE ); END; /
------------------------------------------------------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | Writes | OMem | 1Mem | Used-Mem | Used-Tmp| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 1469 |00:01:35.85 | 283K| 288K| 5872 | | | | | | 1 | HASH GROUP BY | | 1 | 355K| 1469 |00:01:35.85 | 283K| 288K| 5872 | 855K| 855K| 17M (0)| | | 2 | PARTITION RANGE AND | | 1 | 53M| 11M|00:01:19.64 | 283K| 288K| 5872 | | | | | |* 3 | HASH JOIN | | 86 | 53M| 11M|00:01:15.20 | 283K| 288K| 5872 | 972K| 972K| 372K (0)| | |* 4 | TABLE ACCESS FULL | ODD_FLUX_CID | 86 | 1322 | 86 |00:00:00.52 | 172 | 172 | 0 | | | | | |* 5 | HASH JOIN | | 86 | 53M| 11M|00:01:01.45 | 282K| 288K| 5872 | 1889M| 35M| 20M (0)| 12288 | |* 6 | TABLE ACCESS FULL| ODD_ASSURE | 86 | 37M| 7949K|00:00:11.45 | 146K| 146K| 0 | | | | | |* 7 | TABLE ACCESS FULL| ODD_INFO_ADHESION | 86 | 54M| 11M|00:00:19.97 | 136K| 136K| 0 | | | | | ------------------------------------------------------------------------------------------------------------------------------------------------------------
Les NESTED LOOP ont laissé place à des HASH JOIN ce qui permet de faire tourner la requête en 1 minute 35 au lieu de 2h34.
J'espère que ces 2 exemples tirés de la réalité vous permettront de comprendre comment grâce aux statistiques étendues on peut aider l'optimiseur à avoir une connaissance plus intelligente des données et ainsi lui permettre de nous trouver un plan adequat.