Introduction
Autres articles sur DBMS_XPLAN.DISPLAY_CURSOR
Sous Oracle 19, il existe six fonctions pour afficher un plan d'exécution, suivant l'endroit où est stocké celui-ci ; ce sont les fonctions qui commencent par DISPLAY dans l'image ci-dessous.
Pour afficher un plan d'exécution estimé après un EXPLAIN PLAN : la requête n'est pas exécutée
Pour afficher un plan d'exécution réel : la requête est vraiment exécutée. Ce plan peut être en SGA, dans le référentiel AWR, dans une SQL plan baseline ou bien dans un SQL tuning set.
- DISPLAY_AWR
- DISPLAY_CURSOR
- DISPLAY_SQL_PLAN_BASELINE
- DISPLAY_SQLSET
La fonction DIFF_PLAN, quant à elle, compare deux plans d'exécutions et affiche les différences.
Nous nous intéresserons ici uniquement à la fonction DBMS_XPLAN.DISPLAY_CURSOR car c'est elle qui dispose des informations les plus riches et qui est la plus utile pour l'optimisation d'un ordre SQL.
Attention, mon objectif est de vous montrer comment utiliser DBMS_XPLAN.DISPLAY_CURSOR mais pas comment lire un plan d'exécution, il y a d'autres sites excellents pour cela.
Points d'attention
N/A.
Base de tests
Une base Oracle 19 multi-tenants.
Exemples
============================================================================================
Base de test
============================================================================================
Nous utiliserons dans cet article la base de test d'Oracle du schéma HR (Human Ressources). Le SELECT est celui qui affiche les employés des départements d'id 50 et 80.
SQL> show user con_name
USER is "HR"
CON_NAME
------------------------------
ORCL
SQL> select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME, E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID = D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80) order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME;
DEPARTMENT_NAME DEPARTMENT_ID FIRST_NAME LAST_NAME EMPLOYEE_ID
------------------------------ ------------- --------------------
Sales 80 Alberto Errazuriz 147
...
Shipping 50 Winston Taylor 180
79 rows selected.
============================================================================================
La fonction DBMS_XPLAN.DISPLAY_CURSOR
============================================================================================
DBMS_XPLAN.DISPLAY_CURSOR est une fonction a priori simple avec seulement trois paramètres :
- sql_id
- cursor_child_no
- format
DBMS_XPLAN.DISPLAY_CURSOR(sql_id IN VARCHAR2 DEFAULT NULL, cursor_child_no IN NUMBER DEFAULT 0, format IN VARCHAR2 DEFAULT 'TYPICAL');
Mais la partie format est vraiment complexe, comme le montrent ces deux captures écran du site d'Oracle.

Sur une autre page, reprenant beaucoup d'infos de l’image précédente, il y a quelques ajouts, comme ADVANCED et ALL.

Un point à retenir est que, pour afficher le plus d'informations, nous avons tout en haut ADVANCED puis ALL puis ALLSTATS. Ces deux derniers, contrairement à leur nom, n'affichent pas toutes les statistiques. Attention, ADVANCED et ALL sont des formats d'affichage alors que ALLSTATS est un raccourci pour les stats mémoire et de disque dur.
Avec statistics_level=all et le hint gather_plan_statistics, Oracle peut afficher encore plus de stats :-)
============================================================================================
Les colonnes d'un plan d'exécution
============================================================================================
A ma grande surprise, dans la doc Oracle, il est très difficile de trouver une page décrivant le rôle des colonnes d'un plan d'exécution. On le trouve facilement pour la fonction EXPLAIN_PLAN (plan d'exécution estimé) mais pas pour DBMS_XPLAN.DISPLAY_CURSOR (plan d'exécution exécuté)... allez savoir pourquoi...
Sur ce site, nous avons un descriptif des colonnes d'un EXPLAIN PLAN (attention, il s'agit d'un plan d'exécution estimé et non réel mais le sens ne change pas pour un plan réel (à part les mots Estimated)).
https://www.red-gate.com/simple-talk/databases/oracle-databases/execution-plans-part-8-cost-time-etc/

Autres colonnes (doc trouvée sur le net)
- Starts : number of times that operation actually happened.
- Buffers : amount of buffer read/write (IO) performed.
- OMem : estimated size (in bytes) required by this work area to execute the operation completely in memory (optimal execution).
- 1Mem : estimated size (in bytes) required by this work area to execute the operation in a single pass. Memory estimate needed to perform the operation in a single pass (Read/Write from disk (temp) only once), called one-pass execution. A multi-pass execution is when the same data is written to and read from disk more than once.
- Used-Mem : memory (in bytes) used by this work area during the last execution of the cursor. ACTUAL amount of memory used for the operation. You also see some numbers in the brackets for this column. There is a significance for them – If the number is 0, then it was an optimal execution, used only memory and no temporary space. If the number is 1, then it was a one-pass execution. If the number is > 1, it was a multi-pass execution, and that number represents the number of passes.
- Used-Tmp : amount of temporary space used for this operation.
============================================================================================
Les principaux formats : BASIC, TYPICAL, ADAPTIVE, ALL, ADVANCED
============================================================================================
La fonction DBMS_XPLAN.DISPLAY_CURSOR accepte plusieurs valeurs pour le paramètre format. Voyons ce qui est affiché pour chacun d'entre eux, du format le moins verbeux au plus verbeux; cela se voit dans le nombre de lignes affichées en fin de chaque plan (on passe de 23 à 85).
Le SELECT de test a comme sql_id 9420hwvnq6jsj; j'affiche le plan d'exécution uniquement de la première exécution (deuxième paramètre à 0).
En bleu gras, les informations spécifiques au format choisi.
Format BASIC
Ce format est très peu utile car il ne possède que l'opération exécutée et le nom de l'objet concerné, sans aucune statistique.
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('9420hwvnq6jsj',0,'BASIC'));
PLAN_TABLE_OUTPUT
-----------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME,
E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID =
D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80)
order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME
Plan hash value: 2480766633
-------------------------------------------------------------
| Id | Operation | Name |
-------------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT ORDER BY | |
| 2 | NESTED LOOPS | |
| 3 | NESTED LOOPS | |
| 4 | INLIST ITERATOR | |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |
| 6 | INDEX UNIQUE SCAN | DEPT_ID_PK |
| 7 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX |
| 8 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES |
-------------------------------------------------------------
23 rows selected.
Format TYPICAL (valeur par défaut)
C'est le format par défaut. Quatre colonnes de stats en plus du format BASIC et ajout des zones "Predicate Information" et "Note".
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('9420hwvnq6jsj',0,'TYPICAL'));
PLAN_TABLE_OUTPUT
-----------------------------------------
SQL_ID 9420hwvnq6jsj, child number 0
-----------------------------------------
select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME,
E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID =
D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80)
order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME
Plan hash value: 2480766633
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | SORT ORDER BY | | 78 | 2964 | 4 (25)| 00:00:01 |
| 2 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
| 4 | INLIST ITERATOR | | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 2 | 32 | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | DEPT_ID_PK | 2 | | 1 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 7 | | 0 (0)| |
| 8 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 39 | 858 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access(("D"."DEPARTMENT_ID"=50 OR "D"."DEPARTMENT_ID"=80))
7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter(("E"."DEPARTMENT_ID"=50 OR "E"."DEPARTMENT_ID"=80))
Note
-----
- this is an adaptive plan
34 rows selected.
Format ADAPTIVE
Notez que le plan hash value est le même que celui des plans affichés ci-dessus MAIS qu'il y a trois lignes en plus dans le plan d'exécution. On est en mode d'affichage ADAPTIVE et la note dit qu'il s'agit justement d'un Adaptive plan. Dans ce cas, les opérations abandonnées en cours de route par le CBO sont affichées dans ce mode avec, pour les identifier, un tiret devant leur id. Plus de lecture via Google :-)
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('9420hwvnq6jsj',0,'ADAPTIVE'));
PLAN_TABLE_OUTPUT
----------------------------------------
SQL_ID 9420hwvnq6jsj, child number 0
----------------------------------------
select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME,
E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID =
D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80)
order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME
Plan hash value: 2480766633
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | SORT ORDER BY | | 78 | 2964 | 4 (25)| 00:00:01 |
|- * 2 | HASH JOIN | | 78 | 2964 | 3 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
|- 5 | STATISTICS COLLECTOR | | | | | |
| 6 | INLIST ITERATOR | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 2 | 32 | 2 (0)| 00:00:01 |
| * 8 | INDEX UNIQUE SCAN | DEPT_ID_PK | 2 | | 1 (0)| 00:00:01 |
| * 9 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 7 | | 0 (0)| |
| 10 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 39 | 858 | 1 (0)| 00:00:01 |
|- * 11 | TABLE ACCESS FULL | EMPLOYEES | 39 | 858 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
8 - access(("D"."DEPARTMENT_ID"=50 OR "D"."DEPARTMENT_ID"=80))
9 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter(("E"."DEPARTMENT_ID"=50 OR "E"."DEPARTMENT_ID"=80))
11 - filter(("E"."DEPARTMENT_ID"=50 OR "E"."DEPARTMENT_ID"=80))
Note
-----
- this is an adaptive plan (rows marked '-' are inactive)
39 rows selected.
Format ALL
Aucune colonne en plus mais des champs supplémentaires; pas hyper intéressantes de mon point de vue.
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('9420hwvnq6jsj',0,'ALL'));
PLAN_TABLE_OUTPUT
------------------------------------------
SQL_ID 9420hwvnq6jsj, child number 0
------------------------------------------
select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME,
E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID =
D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80)
order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME
Plan hash value: 2480766633
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | SORT ORDER BY | | 78 | 2964 | 4 (25)| 00:00:01 |
| 2 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
| 4 | INLIST ITERATOR | | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 2 | 32 | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | DEPT_ID_PK | 2 | | 1 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 7 | | 0 (0)| |
| 8 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 39 | 858 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
5 - SEL$1 / D@SEL$1
6 - SEL$1 / D@SEL$1
7 - SEL$1 / E@SEL$1
8 - SEL$1 / E@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access(("D"."DEPARTMENT_ID"=50 OR "D"."DEPARTMENT_ID"=80))
7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter(("E"."DEPARTMENT_ID"=50 OR "E"."DEPARTMENT_ID"=80))
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=3) "D"."DEPARTMENT_NAME"[VARCHAR2,30], "E"."FIRST_NAME"[VARCHAR2,20],
"E"."LAST_NAME"[VARCHAR2,25], "E"."DEPARTMENT_ID"[NUMBER,22], "E"."EMPLOYEE_ID"[NUMBER,22]
2 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30],
"E"."EMPLOYEE_ID"[NUMBER,22], "E"."FIRST_NAME"[VARCHAR2,20], "E"."LAST_NAME"[VARCHAR2,25],
"E"."DEPARTMENT_ID"[NUMBER,22]
3 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30],
"E".ROWID[ROWID,10], "E"."DEPARTMENT_ID"[NUMBER,22]
4 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30]
5 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30]
6 - "D".ROWID[ROWID,10], "D"."DEPARTMENT_ID"[NUMBER,22]
7 - "E".ROWID[ROWID,10], "E"."DEPARTMENT_ID"[NUMBER,22]
8 - "E"."EMPLOYEE_ID"[NUMBER,22], "E"."FIRST_NAME"[VARCHAR2,20],
"E"."LAST_NAME"[VARCHAR2,25]
Note
-----
- this is an adaptive plan
60 rows selected.
Format ADVANCED
Advanced permet d'avoir deux nouveaux blocs
- "OUTLINE DATA" donne des infos sur les paramètres de l'optimiseur, comme ALL_ROWS, FIRST_ROWS... utilisés pour cette exécution du SELECT
- "Query Blocks Registry"
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('9420hwvnq6jsj',0,'ADVANCED'));
PLAN_TABLE_OUTPUT
----------------------------------------
SQL_ID 9420hwvnq6jsj, child number 0
----------------------------------------
select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME,
E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID =
D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80)
order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME
Plan hash value: 2480766633
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | SORT ORDER BY | | 78 | 2964 | 4 (25)| 00:00:01 |
| 2 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 78 | 2964 | 3 (0)| 00:00:01 |
| 4 | INLIST ITERATOR | | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 2 | 32 | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | DEPT_ID_PK | 2 | | 1 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 7 | | 0 (0)| |
| 8 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 39 | 858 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
5 - SEL$1 / D@SEL$1
6 - SEL$1 / D@SEL$1
7 - SEL$1 / E@SEL$1
8 - SEL$1 / E@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('19.1.0')
DB_VERSION('19.1.0')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX_RS_ASC(@"SEL$1" "D"@"SEL$1" ("DEPARTMENTS"."DEPARTMENT_ID"))
INDEX(@"SEL$1" "E"@"SEL$1" ("EMPLOYEES"."DEPARTMENT_ID"))
LEADING(@"SEL$1" "D"@"SEL$1" "E"@"SEL$1")
USE_NL(@"SEL$1" "E"@"SEL$1")
NLJ_BATCHING(@"SEL$1" "E"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access(("D"."DEPARTMENT_ID"=50 OR "D"."DEPARTMENT_ID"=80))
7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter(("E"."DEPARTMENT_ID"=50 OR "E"."DEPARTMENT_ID"=80))
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=3) "D"."DEPARTMENT_NAME"[VARCHAR2,30], "E"."FIRST_NAME"[VARCHAR2,20],
"E"."LAST_NAME"[VARCHAR2,25], "E"."DEPARTMENT_ID"[NUMBER,22], "E"."EMPLOYEE_ID"[NUMBER,22]
2 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30],
"E"."EMPLOYEE_ID"[NUMBER,22], "E"."FIRST_NAME"[VARCHAR2,20], "E"."LAST_NAME"[VARCHAR2,25],
"E"."DEPARTMENT_ID"[NUMBER,22]
3 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30],
"E".ROWID[ROWID,10], "E"."DEPARTMENT_ID"[NUMBER,22]
4 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30]
5 - "D"."DEPARTMENT_ID"[NUMBER,22], "D"."DEPARTMENT_NAME"[VARCHAR2,30]
6 - "D".ROWID[ROWID,10], "D"."DEPARTMENT_ID"[NUMBER,22]
7 - "E".ROWID[ROWID,10], "E"."DEPARTMENT_ID"[NUMBER,22]
8 - "E"."EMPLOYEE_ID"[NUMBER,22], "E"."FIRST_NAME"[VARCHAR2,20],
"E"."LAST_NAME"[VARCHAR2,25]
Note
-----
- this is an adaptive plan
Query Block Registry:
---------------------
<q o="2" f="y"><n><![CDATA[SEL$1]]></n><f><h><t><![CDATA[D]]></t><s><![CDATA[SEL$1]]></s></h>
<h><t><![CDATA[E]]></t><s><![CDATA[SEL$1]]></s></h></f></q>
85 rows selected.
============================================================================================
Afficher plus de statistiques : statistics_level=all et gather_plan_statistics
============================================================================================
Le format ADVANCED permet d’afficher énormément d’informations, Id, Operation, Name, Rows, Bytes, Cost (%CPU), Time, en plus des nombreux blocs.
MAIS il y a mieux encore :-) Il est possible d'avoir des statistiques plus précises sur l'exécution du SELECT et voir si le CBO estime correctement les cardinalités :
- statistics_level=all -- utile si on ne peut pas modifier l’ordre SQL en y ajoutant le hint gather_plan_statistics
- gather_plan_statistics -- à utiliser si on a la main sur l'ordre SQL
L'objectif est de collecter le nombre de lignes retournées par chaque nœud du plan d'exécution. En comparant les colonnes A-ROWS (pour Actual Rows) et E-ROWS (pour Estimated Rows), on peut identifier si le CBO estime correctement ou non les cardinalités des phases du plan, ce qui est vital pour choisir le meilleur mode d'accès aux données.
Attention, l'utilisation de ces outils ralentit un peu l'ordre SQL puisque des stats supplémentaires sont collectées. Ils ne sont donc à utiliser que dans une étape de tuning.
A noter qu'avec ces outils, la colonne A-ROWS (nombre réel de lignes ) affiche des durées au dixième de seconde près alors que sans ces outils, le temps est limité à la seconde; c'est clairement insuffisant dans le monde informatique.
statistics_level=all
D'après la doc Oracle https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/STATISTICS_LEVEL.html#GUID-16B23F95-8644-407A-A6C8-E85CADFA61FF "STATISTICS_LEVEL specifies the level of collection for database and operating system statistics. The Oracle Database collects these statistics for a variety of purposes, including making self-management decisions. When the STATISTICS_LEVEL parameter is set to ALL, additional statistics are added to the set of statistics collected with the TYPICAL setting. The additional statistics are timed operating system statistics and plan execution statistics."
SQL> ALTER SESSION SET STATISTICS_LEVEL=ALL;
Session altered.
SQL> show parameter statistics_level
NAME TYPE VALUE
------------------------------------ ----------- --
client_statistics_level string TYPICAL
statistics_level string ALL
Attention, je change l'appel de ma fonction (nous verrons pourquoi dans un autre article) et je vide la SHARED POOL car il y a, après X exécutions du même SELECT, plusieurs curseurs en mémoire pour le même plan d'exécution. Donc en flushant, j'élimine les plus anciens que je ne veux pas afficher. Cette astuce n'est surtout pas à faire en Production car cela va ralentir la base!
SQL> alter system flush SHARED_POOL;
System altered.
SQL> select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME, E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID = D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80) order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME;
DEPARTMENT_NAME DEPARTMENT_ID FIRST_NAME LAST_NAME EMPLOYEE_ID
------------------------------ ------------- -------------------- -------------------------
Sales 80 Alberto Errazuriz 147
...
Shipping 50 Winston Taylor 180
79 rows selected.
Que voit-on? Plusieurs colonnes en plus : Starts, A-Rows, A-Time, Buffers, 0Mem, 1Mem, Used-Mem. Mais il y a eu aussi la disparition de plusieurs colonnes: Cost (%CPU) et Time; la colonne Time est-elle remplacé par A-Time? possible, à voir dans la doc. La colonne E-Rows remplace l'ancienne Rows si on en juge par le nombre de lignes, 78 dans les deux cas. La colonne Starts vous dit combien de fois l'opération a été exécuté; dans notre exemple, la table EMPLOYEES a été accédée 79 fois via l'opération "TABLE ACCESS BY INDEX ROWID".
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('9420hwvnq6jsj',0,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
----------------------------------------
SQL_ID 9420hwvnq6jsj, child number 0
----------------------------------------
select D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME,
E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID =
D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80)
order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME
Plan hash value: 2480766633
-----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 79 |00:00:00.01 | 9 | | | |
| 1 | SORT ORDER BY | | 1 | 78 | 79 |00:00:00.01 | 9 | 11264 | 11264 |10240 (0)|
| 2 | NESTED LOOPS | | 1 | 78 | 79 |00:00:00.01 | 9 | | | |
| 3 | NESTED LOOPS | | 1 | 78 | 79 |00:00:00.01 | 5 | | | |
| 4 | INLIST ITERATOR | | 1 | | 2 |00:00:00.01 | 3 | | | |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 2 | 2 | 2 |00:00:00.01 | 3 | | | |
|* 6 | INDEX UNIQUE SCAN | DEPT_ID_PK | 2 | 2 | 2 |00:00:00.01 | 2 | | | |
|* 7 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 2 | 7 | 79 |00:00:00.01 | 2 | | | |
| 8 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 79 | 39 | 79 |00:00:00.01 | 4 | | | |
------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access(("D"."DEPARTMENT_ID"=50 OR "D"."DEPARTMENT_ID"=80))
7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter(("E"."DEPARTMENT_ID"=50 OR "E"."DEPARTMENT_ID"=80))
Note
-----
- this is an adaptive plan
34 rows selected.
gather_plan_statistics
Pré-requis : il faut pouvoir modifier l'ordre SQL.
Si le paramètre STATISTICS_LEVEL est positionné à ALL, alors il n'est pas nécessaire d'utiliser ce hint.
L'intérêt de ce hint, par rapport au paramètre STATISTICS_LEVEL à ALL, c'est qu'on peut traiter un ordre SQL et un seul. Le problème avec STATISTICS_LEVEL, c'est qu'il s'applique à tous les ordres SQL de la session ou de la base selon qu'on ait fait un ALTER SESSION ou un ALTER SYSTEM.
SQL> show parameter level
NAME TYPE VALUE
---------------------------
...
statistics_level string TYPICAL
SQL> set feedback on sql_id
SQL> select /*+ GATHER_PLAN_STATISTICS */ D.DEPARTMENT_NAME, E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME, E.EMPLOYEE_ID from employees E, departments D where E.DEPARTMENT_ID = D.DEPARTMENT_ID AND (D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80) order by D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME;
DEPARTMENT_NAME DEPARTMENT_ID FIRST_NAME LAST_NAME EMPLOYEE_ID
------------------------------ ------------- -------------------- ------------------------- -----------
Sales 80 Alberto Errazuriz 147
...
Shipping 50 Winston Taylor 180
79 rows selected.
SQL_ID: 16w1adqsd8hnb
Attention au piège : le sql_id a changé, on est passé de 9420hwvnq6jsj à 16w1adqsd8hnb; dans l'ordre SQL. Comme on a ajouté les caractères /*+ GATHER_PLAN_STATISTICS */ dans le SELECT, la fonction de hashage génère un sql_id différent. On retrouve les mêmes colonnes qu'avec statistics_level=all.
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('16w1adqsd8hnb',0,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
--------------------------------------
SQL_ID 16w1adqsd8hnb, child number 0
--------------------------------------
select /*+ GATHER_PLAN_STATISTICS */ D.DEPARTMENT_NAME,
E.DEPARTMENT_ID, E.FIRST_NAME, E.LAST_NAME, E.EMPLOYEE_ID from
employees E, departments D where E.DEPARTMENT_ID = D.DEPARTMENT_ID AND
(D.DEPARTMENT_ID = 50 OR D.DEPARTMENT_ID = 80) order by
D.DEPARTMENT_NAME, E.FIRST_NAME, E.LAST_NAME
Plan hash value: 2480766633
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 79 |00:00:00.01 | 9 | | | |
| 1 | SORT ORDER BY | | 1 | 78 | 79 |00:00:00.01 | 9 | 11264 | 11264 |10240 (0)|
| 2 | NESTED LOOPS | | 1 | 78 | 79 |00:00:00.01 | 9 | | | |
| 3 | NESTED LOOPS | | 1 | 78 | 79 |00:00:00.01 | 5 | | | |
| 4 | INLIST ITERATOR | | 1 | | 2 |00:00:00.01 | 3 | | | |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 2 | 2 | 2 |00:00:00.01 | 3 | | | |
|* 6 | INDEX UNIQUE SCAN | DEPT_ID_PK | 2 | 2 | 2 |00:00:00.01 | 2 | | | |
|* 7 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 2 | 7 | 79 |00:00:00.01 | 2 | | | |
| 8 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 79 | 39 | 79 |00:00:00.01 | 4 | | | |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access(("D"."DEPARTMENT_ID"=50 OR "D"."DEPARTMENT_ID"=80))
7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter(("E"."DEPARTMENT_ID"=50 OR "E"."DEPARTMENT_ID"=80))
Note
-----
- this is an adaptive plan
35 rows selected.
Dans les prochains articles sur DBMS_XPLAN.DISPLAY_CURSOR, nous verrons quelles sont les différentes façons d'appeler cette fonction et quels sont les pièges classiques.