Mohamed Houri’s Oracle Notes

June 19, 2013

Different sql id, different force matching signature, different rows processed with the same plan hash value

Filed under: explain plan — hourim @ 9:07 am

Very recently, two interesting blog articles, here and here, have been published to emphasize the possibility of having a same plan hash value for actually two different execution plans.

Since then, I started opening my eyes for any plan hash value that is shown for two or more execution plans. That’s way, the last week, when I was modeling an example for an outer join in response to a question that came up in the French forum, I was immediately attracted by the following sql I have engineered:

SQL> select
2             d.deptno
3            ,d.dname
4            ,e.hiredate
5      from
6            dept d, emp e
7      where
8            d.deptno = e.deptno(+)
9      AND EXISTS
10                 ( SELECT  NULL
11                   FROM    emp e2
12                   WHERE   e.deptno    = e2.deptno
13                   HAVING  MAX(e2.hiredate) = e.hiredate
14                  -- or e.hiredate is null
15                   )
16       ;

DEPTNO DNAME      HIREDATE
------ ---------- ---------
20    RESEARCH    12/01/83
30    SALES       03/12/81
10    ACCOUNTING  23/01/82

Plan hash value: 2339135578  --> note this plan hash value

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
| Id  | Operation            | Name | Starts | E-Rows | A-Rows |   A-Time   |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |      1 |        |      3 |00:00:00.01 |
|*  1 |  FILTER              |      |      1 |        |      3 |00:00:00.01 |
|*  2 |   HASH JOIN OUTER    |      |      1 |     14 |     15 |00:00:00.01 |
|   3 |    TABLE ACCESS FULL | DEPT |      1 |      4 |      4 |00:00:00.01 |
|   4 |    TABLE ACCESS FULL | EMP  |      1 |     14 |     14 |00:00:00.01 |
|*  5 |   FILTER             |      |     15 |        |      3 |00:00:00.01 |
|   6 |    SORT AGGREGATE    |      |     15 |      1 |     15 |00:00:00.01 |
|*  7 |     TABLE ACCESS FULL| EMP  |     15 |      5 |     70 |00:00:00.01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( IS NOT NULL)
2 - access("D"."DEPTNO"="E"."DEPTNO")
5 - filter(MAX("E2"."HIREDATE")=:B1) --> note this crucial point here
7 - filter("E2"."DEPTNO"=:B1)

And to this a little bit different sql:

SQL> select
2             d.deptno
3            ,d.dname
4            ,e.hiredate
5      from
6            dept d, emp e
7      where
8            d.deptno = e.deptno(+)
9      AND EXISTS
10            ( SELECT  NULL
11              FROM    emp e2
12              WHERE   e.deptno    = e2.deptno
13              HAVING  MAX(e2.hiredate) = e.hiredate
14              or e.hiredate is null  --> this part has been uncommented
15             )
16       ;

DEPTNO DNAME          HIREDATE
---------- -------------- --------
20 RESEARCH       12/01/83
30 SALES          03/12/81
10 ACCOUNTING     23/01/82
40 OPERATIONS

Plan hash value: 2339135578  --> the same plan hash value
-----------------------------------------------------------------------------
| Id  | Operation            | Name | Starts | E-Rows | A-Rows |   A-Time   |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |      1 |        |      4 |00:00:00.01 |
|*  1 |  FILTER              |      |      1 |        |      4 |00:00:00.01 |
|*  2 |   HASH JOIN OUTER    |      |      1 |     14 |     15 |00:00:00.01 |
|   3 |    TABLE ACCESS FULL | DEPT |      1 |      4 |      4 |00:00:00.01 |
|   4 |    TABLE ACCESS FULL | EMP  |      1 |     14 |     14 |00:00:00.01 |
|*  5 |   FILTER             |      |     15 |        |      4 |00:00:00.01 |
|   6 |    SORT AGGREGATE    |      |     15 |      1 |     15 |00:00:00.01 |
|*  7 |     TABLE ACCESS FULL| EMP  |     15 |      5 |     70 |00:00:00.01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( IS NOT NULL)
2 - access("D"."DEPTNO"="E"."DEPTNO")
5 - filter((MAX("E2"."HIREDATE")=:B1 OR :B2 IS NULL)) --> for a different predicate
7 - filter("E2"."DEPTNO"=:B1)

Well, nothing new per regard to the two related blog articles mentioned above.  However it is worth pointing out how to different sql_id with two different force matching signature and producing two different result sets, could end up sharing the same plan hash value of two execution plans differencing by their predicate part as shown below:

select
  sql_id
 ,child_number
 ,force_matching_signature
 ,rows_processed
 ,plan_hash_value
from
v$sql
where sql_text like '%MAX(e2.hiredate)%'
and   sql_text not like '%v$sql%';

SQL_ID        CHILD_NUMBER FORCE_MATCHING_SIGNATURE ROWS_PROCESSED PLAN_HASH_VALUE
------------- ------------ ------------------------ -------------- ---------------
ba39fv7txcsbk            0 6256287308517838235              4      2339135578
b2kggnvz02ctk            0 1563627505656661161              3      2339135578

In this context of plan hash value inspection, Carlos sierra from the Oracle support published a blog article showing that his sqltxplain tool has the ability to consider execution plan using not only the plan hash value but two additional pieces of information which are SQLT Plan Hash Value1 and SQLT Plan Hash Value2. The last information is related to the predicate part which is here the central point of difference between “my” two execution plans.

Well, why not try sqltxtract for this case using sql_id ba39fv7txcsbk (click on the picture to enlarge it)?

Plan hash value2

Spot how the sqltxtract module shows the presence of two execution plans having the same plan hash value (2339135578) but different plan hash value2(62199 and 22135). This plan hash value2 concerns a difference into the access and/or the filter predicates.

But wait, this doesn’t mean that the sql_id ba39fv7txcsbk has necessarily got two different execution plans. All what it clearly indicates is that the plan hash value of this parent sql_id has been seen two times, each time with a different access and/or filter predicates. This is confirmed by the sql scripts given by Carlos sierra which when applied to this particular case gives this:


SQL> start c:\psql_id
Enter value for sql_id: ba39fv7txcsbk

no rows selected

Meaning that this sql_id has not got a difference in the predicate part of its execution plan.


SQL> start c:\phash

Enter value for plan_hash_value: 2339135578

ID  TYPE     SQL_ID         CHILD_NUMBER  PREDICATES
---- -------- -------------- ------------- ------------------------------------------
5   filter  ba39fv7txcsbk    0             (MAX("E2"."HIREDATE")=:B1 OR :B2 IS NULL)
5   filter  b2kggnvz02ctk    0              MAX("E2"."HIREDATE")=:B1

Meaning that this plan_hash_value has got two execution plans having two different predicate parts for two different sql_ids

June 14, 2013

ORA-08102: index key not found: Part II

Filed under: Index — hourim @ 6:52 pm

Last year I wrote a note about how I succeeded to overcome an index key not found error by moving the underlying table not without mentioning that an offline rebuild of the corrupted index reveals to be of no help.  Because, in contrast to an online index rebuild which is based on the table data, an offline index rebuild is based on the index data. And, as far as this data is corrupted in the index, rebuilding the same index with the same data will  produce the same error.

What prompted me to write this article is that, yesterday,  we have been confronted to the same error with the same index in the ACCEPTANCE (beta) database.

I was going to play with tis case by tracing(10046 events) an offline rebuild first and then set the index in an unusable state before rebuilding  it offline when I received an e-mail from a DBA telling that he has successfully rebuilt the culprit index online. Too late.

Fortunately, yes you read it correctly, the day after, the same error occurred again but this time on another index of the same table.

The occasion was then given to me again to check the suggestion made by Jason Bucata (see comment 2) about putting the index in an unusable state and rebuild it offline. As such, i.e. when index is in an unusable state, even if this index is rebuilt offline, Oracle will use the underlying table to reconstruct the corrupted index in contrast to a “valid” index rebuilt offline where the underlying table is not used during this kind of rebuilt.

And by the way, instead of rebuilding the newly corrupted index, I decide to consider all the table indexes (it is not very safe  but I could not take a risk of another day with a new different corrupted index)

select 'alter index ' ||index_name || ' unusable;' from user_indexes where table_name = 'TABLE_XXX';
select 'alter index ' ||index_name || ' rebuild;'  from user_indexes where table_name = 'TABLE_XXX';

The 10046 trace for the rebuild offline of an unusable index identified by its object_id (obj#=245082) belonging to a table identified by its object_id ( obj#=244832) when tkprofed show this:

alter index XXX_IND_NI rebuild

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       75      0.32       0.37          0         96          0           0
Execute      1    182.96     174.86     492687     368598      75682           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       76    183.28     175.23     492687     368694      75682           0

Misses in library cache during parse: 75
Optimizer mode: ALL_ROWS
Parsing user id: 47

Elapsed times include waiting on following events:

Event waited on                             Times   Max. Wait  Total Waited
----------------------------------------   Waited  ----------  ------------
SQL*Net break/reset to client                 150        0.00          0.17
SQL*Net message to client                      76        0.00          0.00
SQL*Net message from client                    76      146.07        169.65
db file scattered read                       6588        0.61          7.27
db file sequential read                       821        0.03          0.08
direct path write temp                        320        0.29          5.81
direct path read temp                        4498        0.06          1.17
log file sync                                   2        0.02          0.02
log file switch completion                      8        0.31          0.73
direct path write                               6        0.00          0.00
reliable message                                1        0.00          0.00
enq: RO - fast object reuse                     1        0.00          0.00
rdbms ipc reply                                 1        0.01          0.01
********************************************************************************

The presence of db file scattered read wait event is a clear indication of a full segment read; and their high number (6588) compared to the db file sequential read (821) suggests that this offline rebuild (of an unusable index) has been done using the underlying table.

Note, by the way, the unusual high number (47) of Misses in Library cache during parse and a cpu time (183 seconds) greater than the elapsed time (175 seconds)

The trace file shows also the following interesting information:

 =====================

PARSING IN CURSOR #2 len=43 dep=0 uid=47 oct=9 lid=47 tim=22486851823983 hv=2687996766 ad='920eda48'
alter index XXX_IND_NI rebuild
END OF STMT

PARSE #2:c=10000,e=6576,p=0,cr=9,cu=0,mis=1,r=0,dep=0,og=1,tim=22486851823977
BINDS #2:
WAIT #2: nam='db file scattered read' ela= 2526 file#=37 block#=21 blocks=58 obj#=244832 tim=22486851828916
WAIT #2: nam='db file scattered read' ela= 4839 file#=37 block#=79 blocks=58 obj#=244832 tim=22486851838736
WAIT #2: nam='db file scattered read' ela= 3139 file#=37 block#=137 blocks=58 obj#=244832 tim=22486851847085
WAIT #2: nam='db file scattered read' ela= 2603 file#=37 block#=195 blocks=58 obj#=244832 tim=22486851853977
WAIT #2: nam='db file scattered read' ela= 5218 file#=37 block#=253 blocks=58 obj#=244832 tim=22486851863154
WAIT #2: nam='db file scattered read' ela= 2313 file#=37 block#=311 blocks=58 obj#=244832 tim=22486851868008
WAIT #2: nam='db file scattered read' ela= 2611 file#=37 block#=369 blocks=58 obj#=244832 tim=22486851875983
WAIT #2: nam='db file scattered read' ela= 3098 file#=37 block#=427 blocks=58 obj#=244832 tim=22486851882593
WAIT #2: nam='db file scattered read' ela= 3194 file#=37 block#=485 blocks=58 obj#=244832 tim=22486851892313
WAIT #2: nam='db file scattered read' ela= 2763 file#=37 block#=543 blocks=58 obj#=244832 tim=22486851901798
WAIT #2: nam='db file scattered read' ela= 3374 file#=37 block#=601 blocks=48 obj#=244832 tim=22486851912129
WAIT #2: nam='db file scattered read' ela= 3214 file#=37 block#=1299 blocks=58 obj#=244832 tim=22486851918241
WAIT #2: nam='db file scattered read' ela= 3015 file#=37 block#=1357 blocks=58 obj#=244832 tim=22486851927379
WAIT #2: nam='db file scattered read' ela= 2787 file#=37 block#=1415 blocks=58 obj#=244832 tim=22486851936055
………..

WAIT #2: nam='db file sequential read' ela= 79 file#=45 block#=3152 blocks=1 obj#=244832 tim=22486853368773
WAIT #2: nam='db file scattered read' ela= 365 file#=45 block#=3154 blocks=55 obj#=244832 tim=22486853369742
WAIT #2: nam='db file scattered read' ela= 464 file#=45 block#=3852 blocks=58 obj#=244832 tim=22486853373874
WAIT #2: nam='db file scattered read' ela= 680 file#=45 block#=3910 blocks=58 obj#=244832 tim=22486853378189
WAIT #2: nam='db file scattered read' ela= 1957 file#=45 block#=3968 blocks=58 obj#=244832 tim=22486853385193
WAIT #2: nam='db file scattered read' ela= 846 file#=45 block#=4026 blocks=58 obj#=244832 tim=22486853390273
WAIT #2: nam='db file scattered read' ela= 456 file#=45 block#=4084 blocks=58 obj#=244832 tim=22486853395209
WAIT #2: nam='db file scattered read' ela= 455 file#=45 block#=4142 blocks=58 obj#=244832 tim=22486853399969
etc….

A very high number of db file scattered read on obj#=244832 which represents the object id of the table to which is attached to corrupted index.

Finally, the bottom line from this blog article is to show that when an index is corrupted (ora-08102) it is possible to rebuild it offline but you should first set it into an unusable state.

June 13, 2013

SQLTXPLAIN: Execution plan and operation order : Exec Ord column

Filed under: Oracle — hourim @ 10:01 am

I’ve recently decided to start exploring the Oracle SQLTXPLAIN tool developed by Carlos Sierra from Oracle support. Up to know I am still exploring the different possibilities that tool offers for diagnosing query response time performance problem. I thought that the best strategy to start with SQLTXPLAIN is to take a query that I have trouble shouted using a traditional method and apply SQLTXTRACT for it to see if I can point out from this SQLTXTRACT output the issue I know it is at the origin of the query performance problem.

While I was exploring the execution plan part of the sqltxtract report applied to my query

ID Exec Ord Operation Go To More Cost2 Estim Card
0 8 SELECT STATEMENT 255 100
1 7  NESTED LOOPS [+] 257
2 5 . NESTED LOOPS [+] 254 100
3 3 .. SORT UNIQUE [+] 103 100
4 2 … TABLE ACCESS BY INDEX ROWID T2 [+] [+] 103 100
5 1 …. INDEX RANGE SCAN T2_I1 [+] [+] 3 100
6 4 .. INDEX RANGE SCAN T1_N1 [+] [+] 2 1
7 6 . TABLE ACCESS BY INDEX ROWID T1 [+] [+] 3 1

I was suddenly attracted by the Exec Ord column. That’s a very nice feature showing the order of the operation as they have been executed by the SQL engine. However, the traditional strategy for reading plans following a parent-child relationship and indentation is not always correct.  This is why I decided to see if the Exec Ord column is correctly reported in the particular cases where the traditional plan reading is wrong. For that I used the example given by Jonathan Lewis in his “constant subquery” case and that you can easily reproduce:

------------------------------------------------------------------
| Id  | Operation             | Name  | Starts | E-Rows | A-Rows |
------------------------------------------------------------------
|*  1 |  FILTER               |       |      1 |        |      0 |
|*  2 |   HASH JOIN RIGHT SEMI|       |      0 |      1 |      0 |
|   3 |    TABLE ACCESS FULL  | F1    |      0 |     20 |      0 |
|   4 |    TABLE ACCESS FULL  | T1    |      0 |  10000 |      0 |
|*  5 |   INDEX UNIQUE SCAN   | F2_PK |      1 |      1 |      0 |
------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( IS NOT NULL)
2 - access("F1"."ID"="T1"."ID" AND "F1"."SMALL_VC"="T1"."SMALL_VC")
5 - access("F2"."ID"=21)

The traditional plan reading will say that the first operation started here is operation 3, followed by operation 4 all combined with operation 2; and finally the operation 5 is triggered by its parent operation 1. But, as explained in Jonathan Lewis post, things have not been executed as this traditional plan reading suggests. Operation 2, 3 and 4 has not been started at all (Starts = 0).

My curiosity dictated me to run sqltxtract for the above query and get the corresponding execution plan together with the Exec Ord column:

ID Exec Ord Operation Go To More Cost2 Estim Card Work Area
0 6 SELECT STATEMENT 55
1 5  FILTER [+] 55
2 3 . HASH JOIN RIGHT SEMI [+] 55 1 [+]
3 1 .. TABLE ACCESS FULL F1 [+] [+] 3 20
4 2 .. TABLE ACCESS FULL T1 [+] [+] 51 10000
5 4 . INDEX UNIQUE SCAN F2_PK [+] [+] 0 1

As you can see from the above execution plan, sqltxtract module is, unfortunately, also reporting a wrong operation order (Exec Ord column) as far as it is showing that the first operations executed here are operation 3 and 4 while actually these two operations have not been executed at all. It would be very nice if the Exec Ord operation could take into account the special cases where the traditional “first child first” rule is not applicable. Among those cases, I know two :  the  “constant subquery” and index scan with a filter predicate applying a subquery

May 28, 2013

Jonathan Lewis philosophies : french translation

Filed under: French-Translation — hourim @ 11:42 am

Les lecteurs francophones peuvent trouver, ci-dessous, une traduction de presque toutes les philosophies de Jonathan Lewis.

Philosophie 1

Il existe certains concepts Oracle très importants si bien qu’ils doivent être gravés dans votre mémoire voltigeant  autour de vos yeux à chaque fois que vous voulez investiguer un problème de performance en SQL. Voici un de ces concepts :

Les histogrammes et les variables de liaison existent pour des raisons diamétralement opposées ; ils ne vont pas fonctionner ensemble sans aide.

Vous utilisez les variables de liaison parce que vous voulez que tout le monde partage le même curseur enfant (child cursor) d’une instruction SQL ; cette instruction SQL, allant être utilisée très fréquemment, tout le monde va accomplir la même petite charge de travail en l’exécutant. Le même plan d’exécution devrait être idéal pour tous, et vous ne voulez pas ré-optimiser cette instruction SQL à chaque réutilisation parce que le coût de son optimisation va être probablement plus élevé que ne le serait celui des ressources nécessaires pour son exécution. Typiquement, on souhaite une large (mais pas exclusive) utilisation des variables de liaison dans des systèmes OLTP.

Vous créez des histogrammes parce que des instructions SQL, qui sont virtuellement identiques, génèrent des charges de travail qui diffèrent énormément, nécessitent des plans d’exécutions différents, le travail fait lors de leur optimisation est négligeable lorsqu’il est comparé à celui fait lors de leur exécution, et, si elles utilisent un mauvais plan d’exécution, elles conduiraient à une énorme perte de ressources. Typiquement, on a besoin de générer des histogrammes dans des systèmes DataWareHouse ou d’aide à la décision où les requêtes peuvent être brutales et très coûteuses.

Notez bien la contradiction : une technologie est supposée vous donner un plan d’exécution unique utilisable par tout le monde  alors que l’autre technologie est supposée donner à chacun son meilleur plan d’exécution

Garder bien cela en mémoire et vous allez être rappelé à être vigilant quant à la création des histogrammes dans des systèmes OLTP. Ainsi,  vous n’allez pas être tenté de transformer absolument chaque valeur littérale en une variable de liaison.

Philosophie 2

Voici une autre idée parmi celles qui sont tellement fondamentales que vous devez toujours les avoir à l’esprit lorsque vous travaillez avec une base de données Oracle

La stratégie fondamentale incorporée dans l’Optimisateur est basée uniquement sur deux points clés:

  • Combien de données vous voulez rapatrier ?
  • Où les avez-vous stockées ?

Si vous tentez d’améliorer la performance d’une requête SQL en jouant avec son contenu jusqu’à ce qu’elle s’exécute assez rapidement, alors vous n’êtes pas sur la bonne voie et vous perdez du temps inutilement.

Si, par contre, vous débutez votre tâche de ’’Tuning’’ en pensant à la taille du volume de données que vous êtes supposé rapatrier et à la nature de la dispersion aléatoire de ces données, alors vous allez réduire le temps nécessaire à trouver la meilleure manière d’acquérir efficacement ces données.

Philosophie 3

La performance d’une requête SQL devrait être liée à la taille du volume de données qui vous intéresse et non à la taille de la base de données.

Si ceci n’est pas votre cas alors vous avez fait une erreur dans l’implémentation physique de votre base de données. Typiquement ceci peut être lié à une pauvre indexation, mais cela peut être aussi dû à des erreurs dans le choix d’autres structures telles que le partitionnement, le clustering, IOT’s ou les vues matérialisées.

Le premier symptôme de cette erreur est la plainte suivante: “au fur et à mesure que le temps avance la base de données devient lente’’

Philosophie 4

Il existe deux aspects critiques dans l’extensibilité des applications informatiques :

  • Dans un mode mono-utilisateur : est-ce que le temps de réponse de cette année sera le même que celui de l’année prochaine (voir Philosophie – 3) ?
  • Dans un mode multiutilisateurs: est-ce que le temps de réponse va être le même lorsque le nombre d’utilisateurs double, triple, quadruple …. ?

Si vous voulez tourner cela en une informelle et compréhensible phrase, Cary Millsap en inventa une bonne au Collaborate2009 : ’’Rapide Maintenant, Rapide plus tard’’

Philosophie 5

Résolution de problème avec Statspack / AWR

Quelque chose doit être en premier dans le ‘’Top 5 Timed Waits’’…. même lorsqu’il n’y a pas de problème de performance.

Philosophie 6

Tests de conformité :

  • Avez-vous testé  en utilisant des données de qualité et  en utilisant toutes les différentes possibilités de données foireuses ?
  • Est-ce que le code fait ce qu’il doit faire… et rien de plus ?

Philosophie 7

Il n’y a pas de secrets :

Au moins, il n’existe pas de secrets impliqués dans le bon fonctionnement d’une base de données. Occasionnellement, une information nouvelle et utile est découverte ; si elle est importante,  cette information va être documentée, discutée et validée en publique. (Elle ne sera pas nécessairement documentée sur Metalink, OTN ou tahiti.oracle.com mais ceci ne fait pas d’elle un secret).

A chaque fois que je vois des personnes faire des présentations sur les ‘’secrets’’,  il s’avère qu’elles ont généralement partagé leur temps entre citer la documentation, énoncer des évidences, faire des erreurs et offrir des généralités  qui nécessitent de prudentes justifications.

J’ai une règle générale simple pour les présentations : plus leur titre est glamour, branché et excitant, moins elles ont de  chances d’être utiles (mais ceci ne va pas m’empêcher de lire le résumé – juste au cas où).

Philosophie 8

Indexes B-tree vs. Indexes Bitmap: la différence la plus critique

  • Un seul index B-tree vous permet d’accéder précisément à un petit volume de données
  • C’est la combinaison de la présence d’un ensemble d’indexes Bitmap qui offre le même degré de précision.

Vous ne devrez pas comparer l’efficacité d’un index bitmap avec l’efficacité d’un index B-tree.

(Inévitablement c’est un peu plus subtile que ça ; vous pouvez créer quelques indexes B-tree peu précis pour éviter les problèmes de verrous liés aux contraintes du type clé étrangère, l’optimisateur peut combiner des indexes B-tree etc… ; mais si vous commencez sur cette base vous allez avoir une vue rationnelle sur comment utiliser les indexes bitmap).

Note : rappelez vous aussi, que les indexes bitmap introduisent des problèmes massifs de concurrence et d’autres surplus de maintenance ; si vous les trouvez dans des systèmes OLTP il y a alors de fortes chances qu’ils y soient en train de causer des problèmes.

Mis à jour 23 déc. 2009 : J’ai écrit un article complémentaire à cette note puisque le sujet que j’ai traité ici semble avoir causé quelques confusions.

Philosophie 9

Il existe une vieille blague concernant un ingénieur, un mathématicien et un philosophe voyageant ensemble dans un train de Londres (Angleterre)  à Cardiff (Pays de Galles)***

Lorsque le train traversa la frontière, l’ingénieur jeta un coup d’œil par la fenêtre  et s’exclama : ‘’Oh, regardez ! Les moutons Gallois sont noirs ‘’.

Le mathématicien répondit :’’Non ; tout ce que vous pouvez prétendre c’est qu’il y a au moins un mouton au pays de Galles qui est de couleur noir’’

Le philosophe les corrigea tous les deux : ‘’Messieurs, tout ce que vous pouvez dire c’est qu’il parait qu’au Pays de Galles, existe un mouton qui semble être noir sur un côté.’’

(Croyez-moi, en 1970, ceci était assez drôle)

La morale de cette histoire est : le meilleur point de vue à considérer lors de la résolution des problèmes d’une base de données est celui du mathématicien ; n’allez pas, comme l’a fait  l’ingénieur, vers des conclusions extrêmes basées uniquement sur une observation ; mais ne  soyez pas, comme l’a fait le philosophe, coincé sur d’aussi petits détails de conformité théorique si bien que des hypothèses raisonnables soient mises de côté.

*** Note: Pour ceux qui ne sont pas familiers avec la géographie du royaume uni (UK) : ‘’Le Royaume uni de la Grande-Bretagne et de l’Irlande du Nord ‘’ représente l’union de l’Angleterre, de l’Ecosse (la grande partie de la moitié haute de l’ile) et du Pays de Galles (le morceau de terre à gauche en excluant la petite et fine partie du bas).

Philosophie 10

La question la plus significative à vous poser lorsque vous êtes en train de réfléchir à l’ajout d’un nouvel index est :

‘’Est-ce que cet index va éliminer significativement plus de travail que ce qu’il va introduire (au moment où cela importe vraiment) ? ‘’

Quelques exemples de ‘’moments qui importent’’

  • Insertion/suppression/mise à jour en vrac
  • Système OLTP avec une activité hautement concurrente
  • Des rapports fréquents nécessitant une très grande précision
  • Test d’acceptation pour les effets collatéraux.

Philosophie 12

Voici une description utile que j’ai récemment entendue du philosophe Daniel Dennett:

Les critères d’une bonne propagande

  1. Elle n’est pas un simple mensonge sans masque
  2. Vous devez être capable de l’énoncer très sérieusement
  3. Elle doit enlever le scepticisme sans exciter la curiosité
  4. Elle doit apparaitre comme profonde

Il semble qu’elle décrit beaucoup de choses que notre industrie publie sur internet.

Philosophie 13

Si vous lisez un commentaire du genre “ X est une mauvaise idée’’ ceci ne veut pas dire ’’ un mécanisme qui, vaguement, n’est pas ‘X’ est une bonne idée’’.

Si, par exemple, je dis

    ’’Les histogrammes ne vont pas bien fonctionner sur des chaines de caractères ayant plus de 32 octets (bytes) de longueur et généralement identiques dans leurs premiers 32 octets ’’

 Ceci n’est absolument pas équivalent à :

’’ C’est une bonne idée de créer des histogrammes sur des chaines de caractères ayant moins de 32 octets (bytes) de longueur’’

Si on était dans un monde purement mathématique on aurait invoqué une logique symbolique en montrant ceci:

(A => B) <=> (¬B => ¬A)

Qui signifie que mon énoncé est équivalant à :
  ’’ si vous avez des histogrammes qui fonctionnent bien, alors les données ne sont pas du type chaine de caractères de plus de 32 octets dont, généralement, les 32 premiers octets possèdent des valeurs identiques’’

Evidement, sous Oracle, vous allez peut-être rencontrer certaines personnes, quelque part, ayant exactement le même type d’histogramme qui semble brillamment fonctionner pour elles.  Cependant, ceci  n’aurait été  possible que parce que l’optimisateur aurait  complètement raté son arithmétique si bien qu’il leur a fourni un plan d’exécution optimal pour une raison complètement erronée. Ces personnes doivent faire attention lors d’une prochaine migration ou prochaine application d’un nouveau patch dans le cas où l’optimisateur y est amélioré.

Philosophie 14

Paraphrasant Yogi Berra:

‘’Ce n’est pas comité jusqu’à ce que ça soit comité”

Si vous vous demandez pourquoi c’est important de rappeler cet inhabituel commentaire –il répond à la question suivante  communément posée:

‘’Est-ce que les redo logs contiennent aussi bien les données non comitées que celles déjà comitées ?’’

La réponse est : oui

Lorsqu’une session est en train de créer un redo change vector elle ne sait pas si elle va finir par faire un commit ou par annuler ce qu’elle a fait (un rollback). Par contre, une session doit être capable de stocker une large liste arbitraire de vecteurs de changements (change vectors) quelque part ; cette liste doit apparaître dans le redo log (idéalement d’une manière instantanée) si la session fait un commit – ainsi Oracle évite des retards lors du commit en déposant les vecteurs de changements dans le redo log au moment de leur création***.

*** Pas strictement correct à partir de 10g et plus où Oracle a introduit un effet retardataire dont le but est de réduire la compétition (la demande) pour l’allocation des redo et lors des copies de redo latches pour de ‘’petites’’ transactions

Philosophie 15

Si vous exécutez une requête qui est supposée retourner un seul enregistrement à partir d’une très large table, et, vous disposez d’un index approprié sur cette table, vous vous attendrez probablement à ce que l’optimisateur d’Oracle identifie l’index et qu’il l’utilise. Si vous changez votre requête de telle sorte qu’elle renvoie tous les enregistrements de cette table (sans tri) vous vous attendrez probablement à ce que l’optimisateur d’Oracle choisisse un full table scan.

Ceci conduit à la très simple idée qui est souvent ignorée

“Quelques fois il suffit juste d’un enregistrement supplémentaire pour passer d’un plan d’exécution utilisant un index scan  à un plan utilisant un full table scan”

Il existe un point où l’optimisateur change d’un accès indexé à un seul enregistrement vers un accès à toute la table pour tous les enregistrements.

Si vous êtes assez chanceux et le modèle de l’optimisateur est parfait il n’y aura aucun effet significatif sur la performance, bien sûr. Mais, nous ne sommes pas aussi chanceux que ça, et c’est pour cette raison que certaines personnes finissent par poser la question : ‘Comment le plan d’exécution est devenu subitement non performant, il n’y a eu aucun changement …. sauf pour un petit supplément de données?”. Tout ce qu’il faut c’est un seul enregistrement (que l’optimisateur reconnait) pour changer d’un plan d’exécution à un autre- et parfois l’optimisateur trouve le mauvais moment pour opérer ce changement.

Philosophie 17

Vous devez comprendre l’application ainsi que ses données

Une récente question dans OTN demandant  des conseils pour faire en sorte que la partie SQL suivante s’exécute rapidement :


delete from toc_node_rel rel
where   not exists (
                    select  *
                     from    toc_rel_meta meta
                     where   rel.rel_id = meta.rel_id
                   );

Voici une très petite liste de questions sur lesquelles il faut se concentrer lors de la recherche des solutions possibles. Il existe une colonne nommée rel_id  dans les deux tables ; les colonnes ayant un “id” dans leur nom tendent à être un tout petit peu spéciales, donc est-ce que ces colonnes sont :

a)  la clé primaire d’une table et la clé étrangère de l’autre (si c’est oui dans quel sens) ?

b)  les clés primaires des deux tables ?

c)  deux clés étrangères d’une clé primaire partagée d’une autre table ?

Jusqu’à ce que vous connaissiez la réponse à ces questions vous ne pouvez pas réellement progresser dans votre cheminement vers la bonne manière d’implémenter le besoin. Et, même lorsque vous aurez eu les réponses, ceci n’est encore qu’une seule étape vers la bonne direction et  un précurseur pour le prochain lot de questions – comme “est ce que les contraintes ont été déclarées et activées ? est-ce que certaines contraintes du type clés étrangères permettent la présence de nulls ? est-ce que certaines contraintes du type clés primaires ont été validées par des indexes non uniques ?’’ . Et nous ne sommes pas encore arrivés aux volumes absolus des données,  à leurs types de regroupements et au volume de données candidat à la suppression.

Note:

Vous pouvez argumenter sur l’existence ou pas des possibilités citées dans un système proprement conçu. Soyez libre de le faire ; ce n’est pas parce qu’un concept est faux en théorie qu’il ne va se produire en pratique.

 Note 2:

Pour  une impression beaucoup plus concrète c sur la petite liste de questions :

Pour l’option  (a) PK/FK – imaginez un cas très simple d’un modèle de commande, sommes nous en train d’essayer de supprimer les produits pour lesquels il n’y a pas de commandes, ou les commandes pour lesquelles on ne stocke pas de  produits (qui n’auraient pas du être introduites dans le système si nous l’avions implémenté correctement) ?

Pour l’option(b) PK/PK – imaginez que notre simple modèle de processus de commande possède une table séparée de livraison qui clone la PK de la table de commande, allons-nous essayer de supprimer les livraisons qui n’ont pas de commande (encore une fois elles ne devraient pas exister, mais qui a dit que ce système a été bien conçus et bien implémenté voir (a)) ?

Pour l’option (c) de la FK partagée – imaginez un processus de commande différent qui permet plusieurs lignes de commandes par commande et qui clone la PK des lignes de commande  pour produire un enregistrement dans la table de livraison, allons nous essayer de supprimer les livraisons qui n’ont pas de lignes de commande (encore une autre possibilité provenant d’un système mal conçu et mal implémenté – mais je suis sûr que je ne suis pas la seule personne à avoir vu de pareils systèmes et un tel code ) ?

Philosophie 18

Une question que je me suis posé récemment est celle-ci:

Quelle est le plus mauvais tort que peut engendrer  la publication d’un article autour d’une technique (caractéristique) d’Oracle  

  1. Dire qu’un concept fonctionne alors qu’il ne fonctionne pas.
  2. Dire qu’un concept ne fonctionne pas alors qu’il  fonctionne.
  3. Dire qu’un concept fonctionne alors que dans certains cas il ne fonctionne pas.
  4. Dire que qu’un concept ne fonctionne pas alors que dans certains cas il fonctionne.

 Je ne pense pas que c’est une question à laquelle il est facile de répondre et, évidement,  ce n’est pas plus facile lorsqu’on commence à considérer le nombre de cas pour lesquels une caractéristique fonctionne ou ne fonctionne pas (combien de cas représentent “quelques cas“), et la fréquence à laquelle les différents cas peuvent apparaître.

 Je ne suis pas sûr qu’il existe une réponse correcte à cette question, par contre, en terme d’impact (temps perdu) j’ai tendance à condamner les affirmations qui stipulent que quelque chose fonctionne alors qu’elle ne fonctionne pas – imaginez les deux extrêmes scénarios suivants :

  •  Quelqu’un est en train d’essayer de résoudre un problème et trouve une publication qui offre une solution qui est supposée bien fonctionner – combien de temps va-t-il perdre en essayant de faire en sorte que cette solution fonctionne parce qu’il ‘’sait’’ qu’elle doit fonctionner.
  • Quelqu’un est tombé par hasard sur une publication qui dit que le mécanisme qu’ils ont déjà implémenté avec succès ne fonctionne pas (ils ne vont pas rechercher l’article, bien évidement, parce qu’ils ont déjà résolu le problème). Ils ne vont  perdre aucun temps – sauf s’ils décident de trouver l’erreur dans l’article.

Il existe, inévitablement,  un contre argument. Quelqu’un serait peut-être en train de chercher une idée stratégique sur comment approcher un stade majeur de leur implémentation, et écarte une ligne d’attaque très utile parce qu’une publication stipule qu’elle (ligne d’attaque) ne va pas fonctionner.  Si cela devait arriver les conséquences auraient eu un impact majeur sur la qualité et sur l’extensibilité du produit final. Par contre, je préfère penser que quelqu’un qui est en train de réfléchir stratégiquement sur des options de conception ne serait pas enclin à négliger une idée sur la base d’un seul article sauf si celui-ci contient quelques très bons arguments et bonnes démonstrations.

Peut-être que le problème n’est pas tellement ce que dit l’article mais plutôt comment il le dit. Ce n’est pas grave de vous tromper ou (comme cela arrive plus fréquemment) de vous tromper partiellement à condition que vous disposiez d’une démonstration claire du travail que vous avez fait pour arriver à votre conclusion. Si vous avez fourni des évidences (et vous êtes arrivé à le présenter proprement) ceci va fournir aux lecteurs l’opportunité de faire des observations comme ‘’ l’exemple stocke des entiers dans des colonne du type varchar2()’’, ‘’ l’exemple utilise un index à deux colonnes, mais il va avoir une seule colonne’’, ‘’l’exemple utilise le tablespsace petitfichier, mais pas grandfichier’’ et, peut-être le plus important  ‘’l’exemple tourne sur 8.1.7.4 et pas sur 11.2.0.3’’

May 18, 2013

Literal, bind variable and adaptive cursor sharing: simplify them please!!!

Filed under: Oracle — hourim @ 11:00 am

When you find yourself very often typing the same set of sql statements you will end up by writing a sql script in which will be collected those sql statements. As such, you will have avoided repetitive sql typing.

When you find yourself very often writing the same set of phrases to explain an Oracle concept you will end up by writing a blog article in which will be collected those phrases. As such, you will be referring to that blog article instead of re-typing the same phrases.

When it is question of pros and cons of using literals, bind variables and cursor sharing, I believe, I’ve reached the point, where writing down my corresponding repetitive phrases become necessary.  So, please, take this article as a summary for me and for those who want to deepen a little bit their knowledge of these interacting concepts.

Let’s start now.

If you want to develop a non scalable and a non available Oracle application running slowly, then you have only one thing to do: “don’t use bind variable’’.  Oracle architecture is so that sharing memory (SGA-Library cache) represents a crucial aspect Oracle engineers have to know and to master. However, as it is always the case with Oracle database, despite this feature is very important it has  few drawbacks that are worth to be known. While bind variables allow sharing of parent cursors (SQL code) they also allow sharing of execution plans (child cursor). Sharing the same execution plan for different bind variables is not always optimal as far as different bind variables can generate different data volume. This is why Oracle introduces bind variable peeking feature which allows Oracle to peek at the bind variable value and give it the best execution plan possible. However, bind variable peeking occurs only at hard parse time which means as far as the query is not hard parsed it will share the same execution plan that corresponds to the last hard parsed bind variable. In order to avoid such situation Oracle introduces in its 11gR2 release, Adaptive Cursor Sharing allowing Oracle to adapt itself to the bind variable when necessary without having to wait for a hard parse of the query.

1.Using Literal variables

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'Y1';

 COUNT(*) MAX(COL2)
 ---------- -----------------------------------------
 1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 761479741

 -------------------------------------------------------------------------------------
 | Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 -------------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
 |   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
 |   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
 |*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
 -------------------------------------------------------------------------------------
 Predicate Information (identified by operation id):
 ---------------------------------------------------
 3 - access("FLAG"='Y1')

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'N1';

 COUNT(*) MAX(COL2)
 ---------- ---------------------------------------------
 49998 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 3693069535
 ---------------------------------------------------------------------------
 | Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------
 |   0 | SELECT STATEMENT   |      |       |       |   216 (100)|          |
 |   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
 |*  2 |   TABLE ACCESS FULL| T1   | 55095 |  1614K|   216   (2)| 00:00:02 |
 ---------------------------------------------------------------------------
 Predicate Information (identified by operation id):
 ---------------------------------------------------
 2 - filter("FLAG"='N1')

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'Y2';

 COUNT(*) MAX(COL2)
 ---------- -----------------------------------------
 1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 761479741
 -------------------------------------------------------------------------------------
 | Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 -------------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
 |   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
 |   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
 |*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
 -------------------------------------------------------------------------------------

 Predicate Information (identified by operation id):
 ---------------------------------------------------
 3 - access("FLAG"='Y2')

 SQL> select /*+ literal_variable */ count(*), max(col2) from t1 where flag = 'N2';

 COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
 49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 Plan hash value: 3693069535
 ---------------------------------------------------------------------------
 | Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------
 |   0 | SELECT STATEMENT   |      |       |       |   216 (100)|          |
 |   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
 |*  2 |   TABLE ACCESS FULL| T1   | 55251 |  1618K|   216   (2)| 00:00:02 |
 ---------------------------------------------------------------------------
 Predicate Information (identified by operation id):
 ---------------------------------------------------
 2 - filter("FLAG"='N2')
 

I executed the same query using 4 different hard coded variables. For each literal variable I got the adequat execution plan. That’s very nice from this point of view. But, if I consult the library cache I will see the damage I have caused

 SQL> select sql_id, substr(sql_text,1,30), executions
   2  from v$sql
   3  where sql_text like '%literal_variable%'
   4  and   sql_text not like '%v$sql%';

 SQL_ID        SUBSTR(SQL_TEXT,1,30)          EXECUTIONS
 ------------- ------------------------------ ----------
 axuhh2rjx0jc7 select /*+ literal_variable */          1---> sql code is not re-executed
 c6yy4pad9fd0x select /*+ literal_variable */          1---> sql code is not shared
 45h3507q5r318 select /*+ literal_variable */          1---> the same sql seems for the CBO
 76q7p8q473cdq select /*+ literal_variable */          1---> to be a new sql statement
 

There is 1 record for each execution.  If you repeat the same sql statement changing only the value of the flag you will end up by having as much as records in v$sql as the number of different literal values you will used.

2. Using bind variables

So what will I point out if I prefer using bind variables instead of these literal ones?

 SQL> var n varchar2(2);
 SQL> exec :n := ’Y1’ ---> bind favoring index range scan
 SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0

-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

SQL> exec :n := ’N1’ ---> bind favoring full table scan
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0
-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

SQL> exec :n := ’Y2’ ---> bind favoring index range scan
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0
-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

SQL> exec :n := ’N2’ ---> bind favoring table scan
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

-------------------------------------
SQL_ID  8xujk8a1g65x6, child number 0
-------------------------------------
Plan hash value: 761479741
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    54 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

Have you already pointed out something very clear? They (4 selects) share the same execution plan which is the plan that was generated for the first hard parsed bind variable ‘Y1′. As far as this one favors an index range scan access it shares that plan with all successive identical queries having the same sql_id. But spot with me how the library cache looks now very attractive

 SQL> select sql_id, substr(sql_text,1,30), executions
  2  from v$sql
  3  where sql_text like '%bind_variable%'
  4  and   sql_text not like '%v$sql%';

 SQL_ID        SUBSTR(SQL_TEXT,1,30)          EXECUTIONS
  ------------- ------------------------------ ----------
  8xujk8a1g65x6 select /*+ bind_variable */ co          4  ---> one sql code and 4 executions
 

Let me, at this particular step, make a break point.

  • SQL statements using literal variables represent a non-sharable SQL which can get the best execution plans each time at a cost in optimization overheads (memory, CPU and latching).
  • SQL statements using bind variables are represented by a unique sql_id (or a very small number of copies) in the library cache statement that are re-executed saving memory and CPU parse time. But this resource saving makes SQL statements sharing the same execution plan; that is the plan corresponding to the first bind value Oracle peeked at for the plan optimization during the hard parse time even if this plan is not optimal for the next bind variable value.

So what? Shall we use literal or bind variables? The best answer I have found to this question is that of Tom Kyte “If I were to write a book about how to build non-scalable Oracle applications, then Don’t use bind variables would be the first and the last chapter”.

3. Adaptive cursor sharing came to the rescue

Adaptive cursor sharing (ACS) is a feature introduced in the Oracle 11g release to allow, under certain circumstances, the Cost Based Optimizer (CBO) to adapt itself, peeks at the bind variable and generate the best plan possible without waiting for a hard parse to occur. Below is presented the ACS working algorithm:

ACS

So far we are using bind variables. Our SQL query is then bind sensitive. Ins’t it?

SQL> alter system flush shared_pool;

SQL> exec :n := 'N1';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 46667 |  1367K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

SQL> exec :n := 'Y1';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 46667 |  1367K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Let’s see now after several executions of the same query if ACS kicks off or not. Remember that the first condition for ACS to kick off is that our cursor has to be bind sensitive. In the next query you should read the “I” prompts as Is_bind_aware , Is_bind_sensitive  and Is_shareable respectively:

SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                 EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ------------------- ---------- ---------------
8xujk8a1g65x6            0 N N Y 9686445671300360182    5         3724264953

After 5 executions the cursor is still not bind sensitive. In fact, to be so, the bind variable should have histograms

SQL> exec dbms_stats.gather_table_stats(USER,'T1',method_opt=>'FOR COLUMNS flag SIZE AUTO',no_invalidate=>FALSE);

SQL> exec :n := 'Y1';
SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

We got an index range scan for the variable that favors an index range scan. That’s fine. Let’s see now if our cursor is bind sensitive

SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                 EXECUTIONS  PLAN_HASH_VALUE
------------- ------------ - - - ------------------- ----------- ---------------
8xujk8a1g65x6            0 N Y Y 9686445671300360182   1         3625400295

Yes it is. But it is not yet bind aware.

SQL> exec :n := 'N2';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

I executed the query with the bind variable that favors a full table scan but I shares the preceding execution plan. Let’s see if our cursor is bind aware


SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
8xujk8a1g65x6            0 N Y Y 9686445671300360182                               2      3625400295

Still not. The query needs a warm up period before being bind aware.  So let’s execute again


SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

SQL_ID  8xujk8a1g65x6, child number 1

Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    30 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 50894 |  1491K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Finally we got a full table scan. Is this due to ACS?


SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
8xujk8a1g65x6            0 N Y Y 9686445671300360182                               2      3625400295
8xujk8a1g65x6            1 Y Y Y 9686445671300360182                               1      3724264953

Yes it is. Look how the second line (child number 1) is bind sensitive, bind aware and shareable. This is how ACS works.

Now, if I execute the same query with a bind variable that favors an index range scan, ACS will give me the INDEX RANGE SCAN plan


SQL> exec :n := 'Y2';

SQL> select /*+ bind_variable */ count(*), max(col2) from t1 where flag = :n;

SQL_ID  8xujk8a1g65x6, child number 2
-------------------------------------
Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    30 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     1 |    30 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

SQL> @c:\is_bind_sens

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
8xujk8a1g65x6            0 N Y N 9686445671300360182                               2      3625400295
8xujk8a1g65x6            1 Y Y Y 9686445671300360182                               2      3724264953
8xujk8a1g65x6            2 Y Y Y 9686445671300360182                               1      3625400295

Spot how a new child cursor (child number 2) has been created and it is bind sensitive, bind aware and shareable. Playing with those bind variables values combinations I ended up by having two child cursors, one for (3724264953) full table scan, and the other one (3625400295) for the index range scan that are both shareable. Thanks to these two child cursors (until they are flushed out, or something disturbs their good working), the CBO will be alternating between the two executions plans giving each bind variable its corresponding execution plan.

For those who want to play with this example, you can use Dominic brooks model reproduced below:

create table t1
(col1  number
,col2  varchar2(50)
,flag  varchar2(2));

insert into t1
select rownum
,      lpad('X',50,'X')
,      case when rownum = 1
then 'Y1'
when rownum = 2
then 'Y2'
when mod(rownum,2) = 0
then 'N1'
else 'N2'
end
from   dual
connect by rownum <= 100000;

create index i1 on t1 (flag);

And the is_bind_sens.sql script is

select sql_id
,      child_number
,      is_bind_aware
,      is_bind_sensitive
,      is_shareable
,      to_char(exact_matching_signature) sig
,      executions
,      plan_hash_value
from   v$sql
where  sql_text like '%bind_variable %'
and    sql_text not like '%v$sql%';

May 5, 2013

SPM baseline selection: how it works?

Filed under: Sql Plan Managment — hourim @ 4:20 pm

In my last post about SQL Plan Management (SPM) I investigated the behavior of Adaptive Cursor Sharing (ACS) feature in the presence of SPM baselines. I will now start focusing my interests on the interaction between the CBO and the SPM plan selection steps using the model of the last post.  During this entire blog article I will be working with an existing SPM baseline which contains two accepted and enabled plans as shown below:

 SIGNATURE            SQL_HANDLE               PLAN_NAME                      ENA ACC  PLAN_ID    DESCRIPTION
 -------------------- ------------------------ ------------------------------ --- --- ---------- -------------------
 1292784087274697613  SYS_SQL_11f0e4472549338d SQL_PLAN_13w748wknkcwd616acf47 YES YES 1634389831 FULL TABLE SCAN(T1)
 1292784087274697613  SYS_SQL_11f0e4472549338d SQL_PLAN_13w748wknkcwd8576eb1f YES YES 2239163167 INDEX RANGE SCAN(i1)
 

And I will try to investigate the following issues

  1. SPM contains two plans (1634389831, 2239163167) and CBO comes up with a plan that matches one of these existing SPM plans
  2. SPM contains two plans (1634389831, 2239163167) and I drop the i1 index (making the plan 2239163167 not anymore reproducible) and execute the query with the bind variable value that favor the index range scan
  3. SPM contains two plans (1634389831, 2239163167) and I add a new index i2 that will produce a new plan which is not in the SPM baseline.
  4. SPM contains two plans (1634389831, 2239163167) and I add a new index i2 that will produce a new plan which is not in the SPM baseline but I drop also the existing index i1 (making the plan 2239163167 not anymore reproducible)

In order to follow my investigation without a lot of difficulties, please would you mind to remember that plan id finishing by 831 (1634389831) corresponds to T1 Table FULL SCAN while the plan id finishing by 167 (2239163167) corresponds to the index i1 RANGE SCAN.

Let’s now embark in the investigations starting by case number 1.

Case1: SPM contains two plans and CBO comes up with a plan that matches one of these existing SPM plans

I executed my query using bind variable = ‘Y1’ which is the bind variable that favor the i1 index range scan. The CBO should come up with the index i1 range scan plan matching the existing plan in the baseline (2239163167). Let’s see how the selection occurs via the 10053 trace file

 SPM: statement found in SMB

The first thing the CBO does is signaling that it realizes the presence of a SPM baseline. Then, follows the classical CBO query optimization

 ****************
 QUERY BLOCK TEXT
 ****************
 select count(*), max(col2) from t1 where flag = :n

 Access path analysis for T1
 ***************************************
 SINGLE TABLE ACCESS PATH
 Single Table Cardinality Estimation for T1[T1]

 Table: T1  Alias: T1
 Card: Original: 100000.000000  Rounded: 9  Computed: 9.10  Non Adjusted: 9.10
 Access Path: TableScan
 Cost:  275.38  Resp: 275.38  Degree: 0
 Cost_io: 272.00  Cost_cpu: 31121440
 Resp_io: 272.00  Resp_cpu: 31121440

 Access Path: index (AllEqRange)
 Index: I1
 resc_io: 2.00  resc_cpu: 18993
 ix_sel: 0.000091  ix_sel_with_filters: 0.000091
 Cost: 2.00  Resp: 2.00  Degree: 1
 Best:: AccessPath: IndexRange                 ---> Best Access Path Index I1 Range Scan with cost =2
 Index: I1
 Cost: 2.00
 ***************************************
 

Where the CBO found that the best access path is the Index Range scan (Index I1 with a cost of 2). However, as far as the CBO has already signaled the presence of SPM plan it knows that it is constrained. It can’t decide to use the plan it comes up with without comparing it to the existing SPM plans. This is why we see the following lines into the same 10053 trace file

 SPM: cost-based plan found in the plan baseline, planId = 2239163167 ---> 167 is the index range scan
 SPM: cost-based plan was successfully matched, planId = 2239163167 --> CBO comes up with a plan that matches a SPM plan
 

That’s all for this case:  when the cost-based generated plan matches one of the existing SPM plans, the CBO will use this plan.

Case2: SPM contains two plans but Idrop the i1 index (making the plan 2239163167 not anymore reproducible) and execute the query with the index bind variable value

The 10053 trace file in this case looks as follows

 SPM: statement found in SMB

****************
QUERY BLOCK TEXT
****************
select count(*), max(col2) from t1 where flag = :n

Access path analysis for T1
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for T1[T1]

Table: T1  Alias: T1
Card: Original: 100000.000000  Rounded: 1  Computed: 0.50  Non Adjusted: 0.50
Access Path: TableScan
Cost:  272.96  Resp: 272.96  Degree: 0

Best:: AccessPath: TableScan        ---> CBO comes up with a FULL TABLE SCAN plan having cost = 273
Cost: 272.96
***************************************

In the absence of the i1 index, the CBO produces a FULL TABLE SCAN as the best access path. As far as this generated plan exists in the baseline it will be used as shown below:

SPM: cost-based plan found in the plan baseline, planId = 1634389831 ---> T1 FULL TABLE SCAN plan
SPM: cost-based plan was successfully matched, planId = 1634389831 --> CBO comes up with a plan that matches a SPM plan

That’s all for this case also:  when the cost-based generated plan matches one of the existing SPM plans, the CBO will use this plan.

Case 3: SPM contains two plans and I add a new index i2 that will produce a new plan which is not in the SPM baseline

In this case I executed my query with a bind variable that favors a fast full scan of the newly created index i2.  The CBO comes up with a plan that doesn’t match any plan in the SPM baseline. This is confirmed here below:

 Access path analysis for T1
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for T1[T1]

Table: T1  Alias: T1
Card: Original: 100000.000000  Rounded: 50563  Computed: 50563.46  Non Adjusted: 50563.46
Access Path: TableScan
Cost:  272.96  Resp: 272.96  Degree: 0

Access Path: index (index (FFS))
Index: I2
resc_io: 44.00  resc_cpu: 18103823
ix_sel: 0.000000  ix_sel_with_filters: 1.000000

Access Path: index (FFS)
Cost:  44.56  Resp: 44.56  Degree: 1
Cost_io: 44.00  Cost_cpu: 18103823
Resp_io: 44.00  Resp_cpu: 18103823

Access Path: index (AllEqRange)
Index: I1
resc_io: 995.00  resc_cpu: 26806643
ix_sel: 0.505635  ix_sel_with_filters: 0.505635
Cost: 995.83  Resp: 995.83  Degree: 1
******** End index join costing ********

Best:: AccessPath: IndexFFS    ---> I2 Index FFS of cost 44 is the best access path
Index: I2
Cost: 44.56
***************************************

The CBO comes up with an INDEX FAST FULL SCAN on the newly created index i2. Sure this will not match an existing plan baseline because we keep repeating that we have only two enabled and accepted plan baselines one for index i1 range scan and the other one for t1 table full scan. It is then very interesting to see how the CBO will react in such a situation.

SPM: planId's of plan baseline are: 2239163167 1634389831
SPM: using qksan to reproduce, cost and select accepted plan, sig = 1292784087274697613
SPM: plan reproducibility round 0 (plan outline + session OFE)
SPM: using qksan to reproduce accepted plan, planId = 2239163167 --> I1 INDEX RANGE SCAN (167)

Since the CBO realizes that it has produced a non matching plan,  It will try to reproduce the existing SPM plan baseline using outline hint and session OFE.

Why the CBO tries to reproduce one of the existing SPM plan?  And why the CBO started by trying to reproduce the index range scan plan first? Why not simply use one of the existing SPM plan?

When the CBO comes up with a plan that is not in the SPM baseline, it considers that something has changed and it is not anymore sure that the existing SPM plans are still reproducible. It also considers that even if the SPM plans are reproducible their corresponding “stored” cost might have changed. This is why, as will we see later, the CBO will not only tries to reproduce the I1 index range scan plan but it will also try to reproduce the full table scan plan in order to compare their costs using current optimizer parameters and table/index statistics. If the two plans are reproducible, then the one having the best current re-computed cost will be used.

We can see this in the 10053 trace file

SELECT /*+ INDEX_RS_ASC ("T1" "I1") */ COUNT(*) "COUNT(*)",MAX("T1"."COL2") "MAX(COL2)"
FROM "MHOURI"."T1" "T1" WHERE "T1"."FLAG"=:B1

Spot how the CBO is injecting the I1 index range scan hint in order to reproduce the SPM index i1 range scan plan

Access path analysis for T1

***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for T1[T1]

Table: T1  Alias: T1
Card: Original: 100000.000000  Rounded: 25000  Computed: 25000.00  Non Adjusted: 25000.00

Access Path: index (AllEqRange)
Index: I1
resc_io: 492.00  resc_cpu: 13254598
ix_sel: 0.250000  ix_sel_with_filters: 0.250000
Cost: 492.41  Resp: 492.41  Degree: 1

Best:: AccessPath: IndexRange
Index: I1
Cost: 492.41  Degree: 1  Resp: 492.41  Card: 25000.00  Bytes: 0
***************************************

SPM: planId in plan baseline = 2239163167, planId of reproduced plan = 2239163167 ---> INDEX_RS plan reproduced
SPM: best cost so far = 492.41, current accepted plan cost = 492.409691           ---> cost = 492
***************************************

Since it has successfully reproduced the I1 index range scan plan and recomputed its corresponding cost, the second step, as indicated above, will be to reproduce the full table scan plan (1634389831) and it corresponding cost

SPM: plan reproducibility round 0 (plan outline + session OFE)
SPM: using qksan to reproduce accepted plan, planId = 1634389831  ---> T1 FULL TABLE SCAN (831)

CBO succeed to reproduce the FULL table scan using the plan outline hint and calculated the “new” cost to be the 272 as shown below:

SPM: planId in plan baseline = 1634389831, planId of reproduced plan = 1634389831
SPM: best cost so far = 272.96, current accepted plan cost = 272.961944

So far, the CBO succeeded to reproduce the two SPM plans baseline and to calculate their corresponding cost. It found that the FULL TABLE SCAN cost (272) is better than the cost of the I1 INDEX RANGE SCAN (492). As such, it has decide to use the SPM FULL TABLE SCAN plan.

The above preceding 10053 trace file lines show clearly  how accepted SPM plans enter in competition when the generated CBO plan is not in the SPM baseline. The CBO doesn’t rely on the cost of the plan stored in the baseline. It has to reproduce all enabled and accepted plans and compares their costs using the current session CBO parameters. 

Now that the CBO succeeded to reproduce both plans and decide to use the FULL TABLE SCAN which is the plan with the smaller cost, there is bizarrely, a supplementary step that consist of re-parsing to generate the best costed reproduced plan i.e. the T1 FULL TABLE SCAN as shown below:

SPM: statement found in SMB
SPM: re-parsing to generate selected accepted plan, planId = 1634389831

Frankly speaking I didn’t understand the need of this last re-parsing step.

That’s all for this case also:  when the cost-based generated plan doesn’t match one of the existing SPM plans, the CBO will reproduce all the SPM plans and compare their cost. The reproduced plan having the best cost will be used.

Case4: SPM contains two plans and I added a new index i2 that will produce a new plan but I also dropped the existing index i1 (making the plan 2239163167 not anymore reproducible)

In this case, I dropped the i1 (flag) index and created a new index i2 (flag, col2) and executed the query with a bind variable that usually was favoring the INDEX i1 RANGE SCAN. Before exploring the corresponding 10053 trace file, let me tell you that the CBO in this configuration will comes up with a new plan using i2 INDEX RANGE SCAN which doesn’t exist in the SPM baseline as shown below:

*******************************************
Peeked values of the binds in SQL statement
*******************************************
----- Bind Info (kkscoacd) -----
Bind#0
oacdty=01 mxl=32(02) mxlc=00 mal=00 scl=00 pre=00
oacflg=03 fl2=1000000 frm=01 csi=178 siz=32 off=0
kxsbbbfp=0d019fe0  bln=32  avl=02  flg=05
value="Y1"   -----> this bind value was favoring I1 INDEX RANGE SCAN before I dropped this index
Access path analysis for T1
***************************************
SINGLE TABLE ACCESS PATH
Table: T1  Alias: T1
Access Path: TableScan                     ----> Full table scan ----> cost 272
Cost:    275.38  Resp: 275.38  Degree: 0
Cost_io: 272.00  Cost_cpu: 31121440
Resp_io: 272.00  Resp_cpu: 31121440

Access Path: index (index (FFS))           ----> I2 index fast full scan ----> cost 44
Index: I2
Access Path: index (FFS)
Cost:  45.97

Cost_io: 44.00  Cost_cpu: 18103823
Resp_io: 44.00  Resp_cpu: 18103823

Access Path: index (Index Only)           ----> I2 index range scan  ---> cost 3
Index: I2
resc_io: 3.00  resc_cpu: 21564
ix_sel: 0.000005  ix_sel_with_filters: 0.000005
Cost: 3.00  Resp: 3.00  Degree: 1

Best: Access Path: Index Range            ----> best access path index range scan I2
Index: I2
Cost: 3.00
***************************************

Now that the CBO comes up with a non matching plan, as always, it will start trying to reproduce all  SPM plans. But this time, despite the index i1 range scan plan is not reproducible because I dropped that i1 index, the CBO will, nevertheless, try to reproduce this plan as shown below:

SPM: plan Ids of plan baseline are: 1634389831 2239163167
SPM: using qksan to reproduce, cost and select accepted plan, sig = 1292784087274697613
SPM: plan reproducibility round 0 (plan outline + session OFE)
SPM: using qksan to reproduce accepted plan, plan Id = 1634389831

During the first round, CBO is trying to reproduce the full table scan

SPM: plan Id in plan baseline = 1634389831, plan Id of reproduced plan = 1634389831
SPM: best cost so far = 275.38, current accepted plan cost = 275.379078

I will skip the  part showing  the FULL TABLE SCAN reproduction because the CBO has successfully reproduced it and there is no added value to present it here.

The next step in this round is to reproduce the I1 index range scan which is in fact impossible (How the CBO can ignore it?)

SPM: plan reproducibility round 0 (plan outline + session OFE)
SPM: using qksan to reproduce accepted plan, plan Id = 2239163167

Look below how the CBO is hinting an index I2 while trying to reproduce a plan with index I1 that is not any more present in the database

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT /*+ INDEX_RS_ASC ("T1" "I2") */ COUNT(*) "COUNT(*)",MAX("T1"."COL2") "MAX(COL2)"
FROM "MOHAMED"."T1" "T1" WHERE "T1"."FLAG"=:B1

And how naturally it will be impossible to reproduce the I1 INDEX RANGES SCAN plan

SPM: plan Id in plan baseline = 2239163167, plan Id of reproduced plan = 3187078153
SPM: failed to reproduce the plan using the following info:
parse_schema name        : MOHAMED
plan_baseline signature  : 1292784087274697613
plan_baseline plan_id    : 2239163167  ---> I1 INDEX RANGE SCAN
plan_baseline hintset    :

hint num  1 len 27 text: IGNORE_OPTIM_EMBEDDED_HINTS
hint num  2 len 37 text: OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
hint num  3 len 22 text: DB_VERSION('11.2.0.1')
hint num  4 len  8 text: ALL_ROWS
hint num  5 len 22 text: OUTLINE_LEAF(@"SEL$1")
hint num  6 len 49 text: INDEX_RS_ASC(@"SEL$1" "T1"@"SEL$1" ("T1"."FLAG"))
SPM: generated non-matching plan:

The CBO failed to reproduce the I1 index range scan and it produces an I2 index range scan instead. However the CBO is still considering that the game is not over and that there is a second chance. This is why we see the following lines about round 2 in the same  10053 trace file

SPM: plan reproducibility round 2 (hinted OFE only)
SPM: using qksan to reproduce accepted plan, plan Id = 2239163167  ---> I1 INDEX RANGE SCAN

And the evident conclusion of a non reproducible plan even in this round 2

SPM: failed to reproduce the plan using the following info:
parse_schema name        : MOHAMED
plan_baseline signature  : 1292784087274697613
plan_baseline plan_id    : 2239163167
plan_baseline hintset    :
hint num  1 len 37 text: OPTIMIZER_FEATURES_ENABLE('11.2.0.1')

SPM: generated non-matching plan:

----- Explain Plan Dump -----
----- Plan Table -----

============
Plan Table
============
-------------------------------------+-----------------------------------+
| Id  | Operation          | Name    | Rows  | Bytes | Cost  | Time      |
-------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT   |         |       |       |    42 |           |
| 1   |  SORT AGGREGATE    |         |     1 |    54 |       |           |
| 2   |   INDEX RANGE SCAN | I2      |   24K | 1318K |    42 |  00:00:01 |
-------------------------------------+-----------------------------------+
Predicate Information:
----------------------
2 - access("FLAG"=:N)

Content of other_xml column
===========================
db_version     : 11.2.0.1
parse_schema   : MOHAMED
plan_hash      : 2583336616
plan_hash_2    : 3187078153 ----> I2 INDEX RANGE SCAN

Outline Data:
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "T1"@"SEL$1" ("T1"."FLAG" "T1"."COL2"))  ---> (FLAG,COL2) are the I2 index columns
END_OUTLINE_DATA
*/
------- END SPM Plan Dump -------

Finally, after two impossible rounds, the CBO recognizes, what it should have recognized much earlier, that it is impossible to reproduce the I1 index range scan and decided to use the unique SPM plan it succeeded to reproduce .i.e. T1 TABLE FULL SCAN not without a extra re-parsing step

SPM: re-parsing to generate selected accepted plan, plan Id = 1634389831 ---> FULL TABLE SCAN

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT /*+ FULL ("T1") */ COUNT(*) "COUNT(*)",MAX("T1"."COL2") "MAX(COL2)"
FROM "MOHAMED"."T1" "T1" WHERE "T1"."FLAG"=:B1
============
Plan Table
============
--------------------------------------+-----------------------------------+
| Id  | Operation           | Name    | Rows  | Bytes | Cost  | Time      |
--------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT    |         |       |       |   275 |           |
| 1   |  SORT AGGREGATE     |         |     1 |    54 |       |           |
| 2   |   TABLE ACCESS FULL | T1      |     1 |    54 |   275 |  00:00:04 |
--------------------------------------+-----------------------------------+
Predicate Information:
----------------------
2 - filter("FLAG"=:N)

Content of other_xml column
===========================
db_version     : 11.2.0.1
parse_schema   : MOHAMED
plan_hash      : 3724264953
plan_hash_2    : 1634389831
Peeked Binds

============
Bind variable information
position=1
datatype(code)=1
datatype(string)=VARCHAR2(32)
char set id=178
char format=1
max length=32
value=Y1

Outline Data:
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "T1"@"SEL$1")
END_OUTLINE_DATA
*/

Bottom line: when using SPM baseline to guarantee plan stability, be warn that when you have several enabled and accepted plan for the same SQL matching signature and, if for any reason those plans become non reproducible,  you might pay a parsing time penalty because the CBO will use two rounds trying to reproduce all SPM plans – even though they are impossible to reproduce–

April 18, 2013

Interpreting Execution Plan

Filed under: explain plan — hourim @ 12:11 pm

I have been confronted to a performance issue with a query that started performing badly (6 sec.  instead of the usual 2 sec. ) following a change request that introduces a new business requirement. Below is the new execution plan stripped to the bare minimum and where table and index names have been a little bit disguised to protect the innocent.  I have manually introduced two aliases (MHO and YAS) in this execution plan so that the predicate part will be easily linked to its corresponding table (I know there is a difference between E-Rows and A-Rows for certain operations; that’s not my intention to deal with  here in this blog post)

--------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                        | Starts | E-Rows | A-Rows |   A-Time   |
---------------------------------------------------------------------------------------------------------------------------
|*  8 |         HASH JOIN OUTER                    |                              |      1 |      1 |   2218 |00:00:05.45 |
|   9 |--->      NESTED LOOPS                      |                              |      1 |      1 |   2218 |00:00:03.44 |
|  10 |           NESTED LOOPS                     |                              |      1 |      1 |   2218 |00:00:03.43 |
|  11 |       ---> VIEW                            | XXX_DEMANDE_MANAGMENT_V      |      1 |    251 |    125K|00:00:02.43 |
|  12 |             UNION-ALL                      |                              |      1 |        |    125K|00:00:02.43 |
|  13 |              NESTED LOOPS                  |                              |      1 |      1 |      0 |00:00:00.01 |
|  14 |               INDEX FULL SCAN              | XXX_CLS_BUR_OPR_UK           |      1 |      1 |      0 |00:00:00.01 |
|  15 |               TABLE ACCESS BY INDEX ROWID  | XXX_ASPECT                   |      0 |      1 |      0 |00:00:00.01 |
|* 16 |                INDEX UNIQUE SCAN           | XXX_BUR_PK                   |      0 |      1 |      0 |00:00:00.01 |
|* 17 |              FILTER                        |                              |      1 |        |    125K|00:00:02.31 |
|* 18 |               HASH JOIN                    |                              |      1 |    126K|    125K|00:00:01.18 |
|  19 |                NESTED LOOPS                |                              |      1 |    251 |    251 |00:00:00.01 |
|  20 |                 VIEW                       | index$_join$_054             |      1 |    251 |    251 |00:00:00.01 |
|* 21 |                  HASH JOIN                 |                              |      1 |        |    251 |00:00:00.01 |
|  22 |                   INDEX FAST FULL SCAN     | XXX_BUR_SOM_FK_I             |      1 |    251 |    251 |00:00:00.01 |
|  23 |                   INDEX FAST FULL SCAN     | XXX_BUR_MSF_BUR_FK_I         |      1 |    251 |    251 |00:00:00.01 |
|* 24 |                 INDEX UNIQUE SCAN          | XXX_SOM_PK                   |    251 |      1 |    251 |00:00:00.01 |
|  25 |                VIEW                        | index$_join$_053             |      1 |    126K|    125K|00:00:00.79 |
|* 26 |                 HASH JOIN                  |                              |      1 |        |    125K|00:00:00.67 |
|  27 |                  INDEX FAST FULL SCAN      | XXX_RIP_PK                   |      1 |    126K|    125K|00:00:00.01 |
|  28 |                  INDEX FAST FULL SCAN      | XXX_RIP_BUR_FK_I             |      1 |    126K|    125K|00:00:00.01 |
|* 29 |               INDEX RANGE SCAN             | XXX_CLS_RIP_FK_I             |    125K|      1 |      0 |00:00:00.66 |
|* 30 |       ---> TABLE ACCESS BY INDEX ROWID     | XXX_DEMANDE_ORDINAIR (MHO)   |    125K|      1 |   2218 |00:00:02.97 |
|* 31 |             INDEX UNIQUE SCAN              | XXX_RIP_PK                   |    125K|      1 |    125K|00:00:00.89 |
|  33 |--->      VIEW (YAS)                        |                              |      1 |     82 |   1218 |00:00:00.08 |
|  34 |           SORT UNIQUE                      |                              |      1 |     82 |   1218 |00:00:00.08 |
--------------------------------------------------------------------------------------------------------------------------

8 - access("YAS"."PK_ID"="MHO"."PK_ID")
30 – filter(("MHO"."CLOSED"<>3 AND TRUNC(INTERNAL_FUNCTION("MHO"."CREATION_DATE"))=TRUNC(TO_DATE('12042013','ddmmrrrr')))
OR (TRUNC(TO_DATE('12042013','ddmmrrrr'))=TO_DATE('01010001','ddmmrrrr') ))

Well, from where am I going to start here?

Hmmm… always the same question when trying to interpret an execution plan.

After looking carefully to that execution plan and to its predicate part (always consider the predicate part) I ended up asking myself the following question:

There is HASH JOIN OUTER (Id 8) between a NESTED LOOPS (Id 9) and the VIEW (YAS – id 33)

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Starts | E-Rows | A-Rows |   A-Time   |
--------------------------------------------------------------------------------------
|*  8 |         HASH JOIN OUTER     |        |      1 |      1 |   2218 |00:00:05.45 |
|   9 |--->      NESTED LOOPS       |        |      1 |      1 |   2218 |00:00:03.44 |
|  33 |--->      VIEW (YAS)         |        |      1 |     82 |   1218 |00:00:00.08 |
--------------------------------------------------------------------------------------

On which a filter operation is applied in order to filter the result by comparing the YAS view with the MHO table via their ”primary key” (PK_ID)

8 - access("YAS"."PK_ID"="MHO"."PK_ID")

That’s seems a little bit strange. Why not a direct HASH JOIN OUTER between the YAS view and the MHO table instead of a JOIN between the YAS view and that NESTED LOOPS (where an access to MHO table is made)?

Have you already pointed out how the predicate part can make you asking good questions?

My second step has been to look at the predicate part of the MHO table access (operation 30) re-printed here below:

30 – filter(("MHO"."CLOSED"<>3 AND TRUNC(INTERNAL_FUNCTION("MHO"."CREATION_DATE"))=TRUNC(TO_DATE('12042013','ddmmrrrr')))
OR (TRUNC(TO_DATE('12042013','ddmmrrrr'))=TO_DATE('01010001','ddmmrrrr') ))

I have an adequate function based index in place that should have been used provided this INTERNAL_FUNCTION has not being used by the CBO

create index XXX_RIP_CREATION_DATE_I on MHO(trunc(creation_date)) ;

This is why I was tempted to force my query to use this index via the appropriate hint. Here below is the resulting execution plan

---------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                        | Starts | E-Rows | A-Rows |   A-Time    |
---------------------------------------------------------------------------------------------------------------------------
|   8 |         NESTED LOOPS                        |                              |      1 |      1 |   2218 |00:00:02.57 |
|*  9 |--->      HASH JOIN OUTER                    |                              |      1 |      1 |   2218 |00:00:02.48 |
|* 10 |      ---> TABLE ACCESS BY INDEX ROWID       | XXX_DEMANDE_ORDINAIR(MHO)    |      1 |      1 |   2218 |00:00:02.34 |
|  11 |            INDEX FULL SCAN                  | XXX_RIP_CREATION_DATE_I      |      1 |    126K|    125K|00:00:01.82 |
|  12 |      ---> VIEW(YAS)                         |                              |      1 |     82 |   1218 |00:00:00.08|
|  13 |            SORT UNIQUE                      |                              |      1 |     82 |   1218 |00:00:00.08 |
|  41 |--->      VIEW                               | XXX_DEMANDE_MANAGMENT_V      |   2218 |      1 |   2218 |00:00:00.08 |
|  42 |           UNION-ALL PARTITION               |                              |   2218 |        |   2218 |00:00:00.08 |
|  43 |            NESTED LOOPS                     |                              |   2218 |      1 |      0 |00:00:00.01 |
|  44 |             TABLE ACCESS BY INDEX ROWID     | XXX_DEMANDE_RESPONSABLE      |   2218 |      1 |      0 |00:00:00.01 |
|* 45 |              INDEX RANGE SCAN               | XXX_CLS_RIP_FK_I             |   2218 |      1 |      0 |00:00:00.01 |
|  46 |             TABLE ACCESS BY INDEX ROWID     | XXX_ASPECT                   |      0 |      1 |      0 |00:00:00.01 |
|* 47 |              INDEX UNIQUE SCAN              | XXX_BUR_PK                   |      0 |      1 |      0 |00:00:00.01 |
|  48 |            NESTED LOOPS                     |                              |   2218 |      1 |   2218 |00:00:00.06 |
|  49 |             NESTED LOOPS                    |                              |   2218 |      1 |   2218 |00:00:00.05 |
|  50 |              TABLE ACCESS BY INDEX ROWID    | XXX_DEMANDE_ORDINAIR         |   2218 |      1 |   2218 |00:00:00.03 |
|* 51 |               INDEX UNIQUE SCAN             | XXX_RIP_PK                   |   2218 |      1 |   2218 |00:00:00.02 |
|* 52 |                INDEX RANGE SCAN             | XXX_CLS_RIP_FK_I             |   2218 |      1 |      0 |00:00:00.01 |
|  53 |              TABLE ACCESS BY INDEX ROWID    | XXX_ASPECT_                  |   2218 |    251 |   2218 |00:00:00.01 |
|* 54 |               INDEX UNIQUE SCAN             | XXX_BUR_PK                   |   2218 |      1 |   2218 |00:00:00.01 |
|* 55 |             INDEX UNIQUE SCAN               | XXX_SOM_PK                   |   2218 |     36 |   2218 |00:00:00.01 |
---------------------------------------------------------------------------------------------------------------------------
9 - access("YAS"."PK_ID"="MHO"."PK_ID")
10 - filter(("MHO"."CLOSED"<>3) AND "PRI"."SYS_NC00047$"=TRUNC(TO_DATE('12042013','ddmmrrrr'))) OR
(TRUNC(TO_DATE('12042013','ddmmrrrr'))=TO_DATE('01010001','ddmmrrrr')))

That is a better step accomplished (from 5,45 to 2,57 seconds). Isn’t it?  Look now how my HASH OUTER JOIN became

------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                     | Starts | E-Rows | A-Rows |   A-Time   |
------------------------------------------------------------------------------------------------------------------
|   8 |         NESTED LOOPS                  |                          |      1 |      1 |   2218 |00:00:02.57 |
|*  9 |--->      HASH JOIN OUTER              |                          |      1 |      1 |   2218 |00:00:02.48 |
|* 10 |      ---> TABLE ACCESS BY INDEX ROWID | XXX_DEMANDE_ORDINAIR(MHO)|      1 |      1 |   2218 |00:00:02.34 |
|  11 |            INDEX FULL SCAN            | XXX_RIP_CREATION_DATE_I  |      1 |    126K|    125K|00:00:01.82 |
|  12 |      ---> VIEW(YAS)                   |                          |      1 |     82 |   1218 |00:00:00.08 |
------------------------------------------------------------------------------------------------------------------

As I wanted it to be: directly between the MHO table and the YAS view.

But wait a moment please. It seems for me that this INDEX FULL SCAN operation is still to be tuned.  Do you know why? Because this operation is feeding back its parent operation (id 10) with 125,000 rowids of which only 2218 records are kept. 98% of those rowids are thrown away by the filter operation n° 10

10 - filter(("MHO"."CLOSED"<>3) AND "PRI"."SYS_NC00047$"=TRUNC(TO_DATE('12042013','ddmmrrrr'))) OR
(TRUNC(TO_DATE('12042013','ddmmrrrr'))=TO_DATE('01010001','ddmmrrrr')))

Clearly this is a waste of time and resource. It is better to have a more precise index or to not use that XXX_RIP_CREATION_DATE_I index at all.  But how can I  (see this is another question again) make the CBO generating the HASH JOIN OUTER I want without forcing the use of that function based index?

Okay…

It’s time to look to the content of the query. The part of the query involving my two tables join looks like this

select
 mho.*
,abc.col1
,abc.col2
from
  XXX_DEMANDE_ORDINAIR       mho
, XXX_DEMANDE_MANAGMENT_V    abc
, my_view                    yas
where  mho.pk        = abc.pk
and    mho.pk_id     = yas.pk_id(+)
etc…

The view YAS is not selected from. It should be taken out from the join and put into the where clause as an EXISTS condition.

select
mho.*
,abc.col1
,abc.col2
from
 XXX_DEMANDE_ORDINAIR       mho
,XXX_DEMANDE_MANAGMENT_V    abc
where  mho.pk        = abc.pk
and  exists (select null
             from  my_view yas
             where mho.pk_id  = yas.pk_id)
etc…

I was going to change this when one of my colleagues suggested me to change the above query as follows (please spot the difference there is only a (+) added)

select
 mho.*
,abc.col1
,abc.col2
from
 XXX_DEMANDE_ORDINAIR       mho
,XXX_DEMANDE_MANAGMENT_V    abc
,my_view                    yas
where  mho.pk        = abc.pk(+) --> This will not change the result because I know there is always a record in abc table
and    mho.pk_id     = yas.pk_id(+)
etc…

Doing so, the ABC table will not be considered by the CBO as the driving table because it is the table that is outer joined (is this correct? I have to admit that I need to test it deeply in order to be sure enough about that fact).  As such, the CBO will directly join the MHO table with the YAS view first (this is what I want in fact) and then outer join the result to the third table.

Anyway, I did as suggested and here below what I ended up with

---------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name                     | Starts | E-Rows | A-Rows |   A-Time   |
--------------------------------------------------------------------------------------------------------
|   9 |         NESTED LOOPS          |                          |      1 |      1 |   2218 |00:00:01.07 |
|* 10 |--->      HASH JOIN OUTER      |                          |      1 |      1 |   2218 |00:00:00.99 |
|  11 |       -->  TABLE ACCESS FULL  | XXX_DEMANDE_ORDINAIR(MHO)|      1 |      1 |   2218 |00:00:00.27 |
|  12 |       -->  VIEW(YAS)          |                          |      1 |     82 |   1218 |00:00:00.08 |
---------------------------------------------------------------------------------------------------------
9 - access("YAS"."PK_ID"="MHO"."PK_ID")
10 – filter(("MHO"."CLOSED"<>3 AND TRUNC(INTERNAL_FUNCTION("MHO"."CREATION_DATE"))=TRUNC(TO_DATE('12042013','ddmmrrrr')))
OR (TRUNC(TO_DATE('12042013','ddmmrrrr'))=TO_DATE('01010001','ddmmrrrr') ))

The query is now responding better than before the change request. Doing a full table scan on MHO table in this case is better than to access it via the existing non precise function based index and then filter the returned rows(by rowid) to through 97% of them.

Bottom line: the goal of this article is to show how important the predicate part can be in tuning a query via its execution plan. I started questioning myself from the join predicate part followed by the use of an adequate index and finally I ended up by searching the best  order of the table  join

April 4, 2013

What can impeach Adaptive Cursor Sharing kicking off?

Filed under: Sql Plan Managment — hourim @ 10:54 am

I ended my last post about the interaction between ACS and SPM by the following observation

How could a creation of an extra index disturb the ACS behavior?

Well, it seems that there is a different combination which leads to this situation. Instead of jumping to a conclusion that might be wrong I prefer presenting my demo upon which I will make a proposition and let you (readers thanks in advance for that) criticizing what I tend to affirm.

For sake of simplicity, the following sql against v$sql will be referred to as is_bind_aware.sql.

SQL > select sql_id
2    , child_number
3    , is_bind_aware
4    , is_bind_sensitive
5    , is_shareable
6    , to_char(exact_matching_signature) sig
7    , executions
8    , plan_hash_value
9    from v$sql
10    where sql_id = '731b98a8u0knf';

The model used for this demo can be found in Dominic Brook’s article and I will start from here

SQL > exec :n := 'N2'; --> FULL TABLE SCAN

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 2
-------------------------------------
select count(*), max(col2) from t1 where flag = :n

Plan hash value: 3724264953

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    54 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 50110 |  2642K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("FLAG"=:N)

Note
-----
- SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement

SQL > exec :n := 'Y2'; --> INDEX RANGE SCAN

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 3
-------------------------------------
select count(*), max(col2) from t1 where flag = :n
Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |     9 |   486 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |     9 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

Note
-----
- SQL plan baseline SQL_PLAN_13w748wknkcwd8576eb1f used for this statement

After several executions of the above sql, using alternately index and full table scan bind variables, I ended up with the following situation:

SQL > @is_bind_aware.sql

SQL_ID        CHILD_NUMBER I I I SIG                EXECUTIONS  PLAN_HASH_VALUE
------------- ------------ - - - ------------------ ----------- -----------------
731b98a8u0knf 0            N Y N 1292784087274697613 2          3724264953
731b98a8u0knf 1            Y Y N 1292784087274697613 1          3625400295
731b98a8u0knf 2            Y Y Y 1292784087274697613 1          3724264953   --> TABLE FULL SCAN
731b98a8u0knf 3            Y Y Y 1292784087274697613 1          3625400295   --> INDEX RANGE SCAN

SQL > select * from v$sql_cs_statistics where sql_id = '731b98a8u0knf';

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED
-------- ---------- ------------- ------------ ------------------- - ---------- --------------
22A4D04C 2443201166 731b98a8u0knf            3          3066078819 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            2          3938583969 Y          1          50000
22A4D04C 2443201166 731b98a8u0knf            1          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            0          1896453392 Y          1          50000

SQL > select * from v$sql_cs_histogram where sql_id = '731b98a8u0knf';

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER  BUCKET_ID      COUNT
-------- ---------- ------------- ------------ ---------- ----------
22A4D04C 2443201166 731b98a8u0knf            3          0          1 --> rows_processed < 1,000
22A4D04C 2443201166 731b98a8u0knf            3          1          0
22A4D04C 2443201166 731b98a8u0knf            3          2          0
22A4D04C 2443201166 731b98a8u0knf            2          0          0
22A4D04C 2443201166 731b98a8u0knf            2          1          1 --> 1,000 <rows_processed <1,000,000
22A4D04C 2443201166 731b98a8u0knf            2          2          0
22A4D04C 2443201166 731b98a8u0knf            1          0          1
22A4D04C 2443201166 731b98a8u0knf            1          1          0
22A4D04C 2443201166 731b98a8u0knf            1          2          0
22A4D04C 2443201166 731b98a8u0knf            0          0          1
22A4D04C 2443201166 731b98a8u0knf            0          1          1
22A4D04C 2443201166 731b98a8u0knf            0          2          0

Two child cursor(2 and 3) that are (a) shareable (b) bind sensitive and (c) bind aware so that ACS can associate each bind variable to it’s a corresponding child number and hence the execution plan that best fits each bind variable.

Up to this point,  ACS is working very well in presence of a SPM baseline

SQL > select
2      to_char(signature) signature
3    , sql_handle
4    , plan_name
5    , enabled
6    , accepted
7    from dba_sql_plan_baselines
8    where signature = 1292784087274697613;

SIGNATURE                                SQL_HANDLE                     PLAN_NAME                      ENA ACC
---------------------------------------- ------------------------------ ------------------------------ --- ---
1292784087274697613                      SYS_SQL_11f0e4472549338d       SQL_PLAN_13w748wknkcwd616acf47 YES YES
1292784087274697613                      SYS_SQL_11f0e4472549338d       SQL_PLAN_13w748wknkcwd8576eb1f YES YES

I have two enabled and accepted sql plan baseline (one, SQL_PLAN …eb1f, for the index range scan and the other one, SQL_PLAN … acf47, for the table full scan). Now, I will create an extra index(i2) in addition to the existing i1 index  and I will continue my selects


SQL > create index i2 on t1(flag,col2) compress;

Index created.

I will then first execute my query for FULL TABLE SCAN bind variable

SQL > exec :n := 'N1';

PL/SQL procedure successfully completed.

SQL >select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 2
An uncaught error happened in prepare_sql_statement : ORA-01403: no data found
NOTE: cannot fetch plan for SQL_ID: 731b98a8u0knf, CHILD_NUMBER: 2
Please verify value of SQL_ID and CHILD_NUMBER;
It could also be that the plan is no longer in cursor cache (check v$sql_plan)

This error is an  indication that something went abnormally as already notified by the Oracle Optimizer blog.


SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 2
-------------------------------------
select count(*), max(col2) from t1 where flag = :n
Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    54 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 49872 |  2629K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("FLAG"=:N)

Note
-----
- SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement

For the sake of clarity I will present below the results of is_bind_aware.sql before the creation of the I2 index and after its creation

SQL > @is_bind_aware.sql  --> before the index creation

SQL_ID        CHILD_NUMBER I I I SIG                EXECUTIONS  PLAN_HASH_VALUE
------------- ------------ - - - ------------------ ----------- -----------------
731b98a8u0knf 0            N Y N 1292784087274697613 2          3724264953
731b98a8u0knf 1            Y Y N 1292784087274697613 1          3625400295
731b98a8u0knf 2            Y Y Y 1292784087274697613 1          3724264953   --> TABLE FULL SCAN
731b98a8u0knf 3            Y Y Y 1292784087274697613 1          3625400295   --> INDEX i1 RANGE SCAN

SQL > @is_bind_aware.sql  --> after the index creation

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
731b98a8u0knf            0 N Y N 1292784087274697613                               2      3724264953
731b98a8u0knf            1 Y Y N 1292784087274697613                               1      3625400295
731b98a8u0knf            2 N N Y 1292784087274697613                               1      3724264953 --> TABLE FULL SCAN

Wow!!! Child cursor n° 3 has gone while child cursor n° 2, despite it is still shareable, becomes however not bind sensitive and not bind aware. And how this has influenced the ACS view?


SQL > select * from v$sql_cs_statistics where sql_id = '731b98a8u0knf'; --> before the index creation

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED
-------- ---------- ------------- ------------ ------------------- - ---------- --------------
22A4D04C 2443201166 731b98a8u0knf            3          3066078819 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            2          3938583969 Y          1          50000
22A4D04C 2443201166 731b98a8u0knf            1          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            0          1896453392 Y          1          50000

SQL > select * from v$sql_cs_statistics where sql_id = '731b98a8u0knf'; --> after the index creation

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED
-------- ---------- ------------- ------------ ------------------- - ---------- --------------
22A4D04C 2443201166 731b98a8u0knf            1          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            0          1896453392 Y          1          50000

No trace of child cursor n ° 2 or n ° 3 in this view while the presence of child cursor n ° 0 and n ° 1 can be considered as obsolete because they represent a non shareable cursors.

Let’s continue executing the query this time using the INDEX RANGE SCAN bind variable

SQL > exec :n := 'Y1'

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 2
-------------------------------------
select count(*), max(col2) from t1 where flag = :n
Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    54 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 49872 |  2629K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("FLAG"=:N)

Note
-----
- SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement

Repeat the same sql several times to see if  ACS will kick off and produce the INDEX RANGE SCAN plan  (the one identified into the SPM baseline SQL_PLAN …eb1f)

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

SQL > /

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

SQL > /

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

… Execute this several times

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
--------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 2
-------------------------------------
select count(*), max(col2) from t1 where flag = :n
Plan hash value: 3724264953
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   275 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    54 |            |          |
|*  2 |   TABLE ACCESS FULL| T1   | 49872 |  2629K|   275   (2)| 00:00:04 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("FLAG"=:N)

Note
-----
- SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement

No way for the CBO to produce the INDEX RANGE SCAN Plan so that the SPM will select it. And why the CBO is unable to produce the INDEX RANGE SCAN plan? There might be two answers to that question (a) either the ACS is working well but it is producing a plan that is not in the SPM and hence it is constrained or (b) the ACS is not working and the CBO is always sharing the existing FULL TABLE SCAN until a hard parse occurs. Let see first if the ACS is working well

SQL> @is_bind_aware

SQL_ID        CHILD_NUMBER I I I SIG                  EXECUTIONS  PLAN_HASH_VALUE
------------- ------------ - - - -------------------- ----------- -----------------
731b98a8u0knf  0           N Y N 1292784087274697613  2           3724264953
731b98a8u0knf  1           Y Y N 1292784087274697613  1           3625400295
731b98a8u0knf  2           N N Y 1292784087274697613  18          3724264953

SQL > select * from v$sql_cs_statistics where sql_id = '731b98a8u0knf';

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED
-------- ---------- ------------- ------------ ------------------- - ---------- --------------
22A4D04C 2443201166 731b98a8u0knf            1          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            0          1896453392 Y          1          50000

ACS is not working!!!

What if I disable the use of sql baseline?

SQL > show parameter '%baseline%'

NAME                                 TYPE        VALUE
------------------------------------ ----------- -------------------------------------------------
optimizer_capture_sql_plan_baselines boolean     FALSE
optimizer_use_sql_plan_baselines     boolean     TRUE

SQL > alter session set optimizer_use_sql_plan_baselines = FALSE;

Session altered.

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 3
-------------------------------------
select count(*), max(col2) from t1 where flag = :n
Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |    18 |   972 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |    18 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("FLAG"=:N)

SQL > @is_bind_aware

SQL_ID        CHILD_NUMBER I I I SIG                                      EXECUTIONS PLAN_HASH_VALUE
------------- ------------ - - - ---------------------------------------- ---------- ---------------
731b98a8u0knf            0 N Y N 1292784087274697613                               2      3724264953
731b98a8u0knf            1 Y Y N 1292784087274697613                               1      3625400295
731b98a8u0knf            2 N N Y 1292784087274697613                              18      3724264953
731b98a8u0knf            3 N Y Y 1292784087274697613                               1      3625400295

SQL > select * from v$sql_cs_statistics where sql_id = '731b98a8u0knf';

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED
-------- ---------- ------------- ------------ ------------------- - ---------- --------------
22A4D04C 2443201166 731b98a8u0knf            3          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            1          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            0          1896453392 Y          1          50000

The new plan is not due to ACS because the corresponding child cursor n°3 is not yet bind aware. So this new plan is due to a hard parse. Let’s continue with the FULL TABLE SCAN bind variable

SQL > exec :n := 'N1'

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 3
select count(*), max(col2) from t1 where flag = :n
Plan hash value: 3625400295
-------------------------------------------------------------------------------------
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |      |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |      |     1 |    54 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1   |    18 |   972 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | I1   |    18 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

SQL > select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
---------- --------------------------------------------------
49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

SQL_ID  731b98a8u0knf, child number 4
-------------------------------------
select count(*), max(col2) from t1 where flag = :n
Plan hash value: 2348726875

------------------------------------------------------------------------------
| Id  | Operation             | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |       |       |    46 (100)|          |
|   1 |  SORT AGGREGATE       |      |     1 |    54 |            |          |
|*  2 |   INDEX FAST FULL SCAN| I2   | 49872 |  2629K|    46   (5)| 00:00:01 |
------------------------------------------------------------------------------

It seems that ACS is back.


SQL >@is_bind_aware

SQL_ID        CHILD_NUMBER I I I SIG                  EXECUTIONS  PLAN_HASH_VALUE
------------- ------------ - - - -------------------- ----------- ----------------
731b98a8u0knf    0         N Y N 1292784087274697613  2           3724264953
731b98a8u0knf    1         Y Y N 1292784087274697613  1           3625400295
731b98a8u0knf    2         N N Y 1292784087274697613  18          3724264953
731b98a8u0knf    3         N Y Y 1292784087274697613  2           3625400295
731b98a8u0knf    4         Y Y Y 1292784087274697613   1          2348726875  --> INDEX FAST FULL SCAN

SQL > select * from v$sql_cs_statistics where sql_id = '731b98a8u0knf';

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED
-------- ---------- ------------- ------------ ------------------- - ---------- --------------
22A4D04C 2443201166 731b98a8u0knf            4          1896453392 Y          1          50000 -->INDEX FAST FULL S.
22A4D04C 2443201166 731b98a8u0knf            3          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            1          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            0          1896453392 Y          1          50000

ACS is in fact really back. After several executions I have the following ACS picture


SQL >@is_bind_aware

SQL_ID        CHILD_NUMBER I I I SIG                     EXECUTIONS  PLAN_HASH_VALUE
------------- ------------ - - - ----------------------------------- ----- ---------
731b98a8u0knf            0 N Y N 1292784087274697613      2          3724264953
731b98a8u0knf            1 Y Y N 1292784087274697613      1          3625400295
731b98a8u0knf            2 N N Y 1292784087274697613      18         3724264953
731b98a8u0knf            3 N Y N 1292784087274697613      2          3625400295
731b98a8u0knf            4 Y Y Y 1292784087274697613      1          2348726875 --> INDEX i2 FAST FULL SCAN
731b98a8u0knf            5 Y Y Y 1292784087274697613      1          3625400295 --> INDEX i1 RANGE SCAN

SQL > select * from v$sql_cs_statistics where sql_id = '731b98a8u0knf';

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED
-------- ---------- ------------- ------------ ------------------- - ---------- --------------
22A4D04C 2443201166 731b98a8u0knf            5          3066078819 Y          1              3  --> INDEX i1 RANGE SCAN
22A4D04C 2443201166 731b98a8u0knf            4          1896453392 Y          1          50000  --> INDEX i2 FFS
22A4D04C 2443201166 731b98a8u0knf            3          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            1          2780714206 Y          1              3
22A4D04C 2443201166 731b98a8u0knf            0          1896453392 Y          1          50000

SQL > select * from v$sql_cs_histogram where sql_id = '731b98a8u0knf';

ADDRESS  HASH_VALUE SQL_ID        CHILD_NUMBER  BUCKET_ID      COUNT
-------- ---------- ------------- ------------ ---------- ----------
22A4D04C 2443201166 731b98a8u0knf            5          0          1   ---> bucket id 0 incremented
22A4D04C 2443201166 731b98a8u0knf            5          1          0
22A4D04C 2443201166 731b98a8u0knf            5          2          0
22A4D04C 2443201166 731b98a8u0knf            4          0          0
22A4D04C 2443201166 731b98a8u0knf            4          1          1   ---> bucket id 1 incremented
22A4D04C 2443201166 731b98a8u0knf            4          2          0
22A4D04C 2443201166 731b98a8u0knf            3          0          1
22A4D04C 2443201166 731b98a8u0knf            3          1          1
22A4D04C 2443201166 731b98a8u0knf            3          2          0
22A4D04C 2443201166 731b98a8u0knf            1          0          1
22A4D04C 2443201166 731b98a8u0knf            1          1          0
22A4D04C 2443201166 731b98a8u0knf            1          2          0
22A4D04C 2443201166 731b98a8u0knf            0          0          1
22A4D04C 2443201166 731b98a8u0knf            0          1          1
22A4D04C 2443201166 731b98a8u0knf            0          2          0

The post is becoming long and may be annoying so I will stop here not without mentioning that I did played with the demo setting the optimizer_capture_sql_plan_baselines to TRUE/FALSE and observing the behavior of ACS through its corresponding views and it seems that adding an extra index generates a new sql plan baseline that is not into the SPM and influence a little bit the work of ACS without knowing the exact reason.

April 1, 2013

Sql Plan Mangement(SPM) and Adaptive Cursor Sharing(ACS) : My résumé

Filed under: Sql Plan Managment — hourim @ 9:40 am

I read Dominic Brook’s interesting article about Adaptive Cursor Sharing and SQL Plan Baseline. I, then, have read the also interesting follow-up blog article written by one of those modest and smart Oracle guys Coskan Gundogar which he has entitled Adaptive Cursor Sharing with SQL Plan Baselines – Bind Sensitiveness. Finally, I have ended up my “SPM-ACS collaboration Giro” with the Optimizer blog article entitled How do adaptive cursor sharing and SQL Plan Management interact

Let me start by presenting the conclusions of these articles respectively

Dominic’s conclusion

Even with multiple plans in the a baseline, if your ACS information is flushed or ages out of the cache, you’re going to have to repeat the relevant executions required to get that ACS information back. Baselines can’t act as a shortcut to bringing back in that ACS feedback.

Coskan’s conclusion

I personally think they work perfectly fine together but I also wish if Oracle gives option to hold this runtime monitoring info in SYSAUX for env where people can accommodate more data in SYSAUX. This will save a lot of time for the initial loads.

Optimizer group conclusion

If a child cursor is bind-aware, the decision to share or not is made irrespective of whether the query is controlled by SPM.  But once the    query and its current bind values are sent to the optimizer for optimization, SPM constrains the optimizer’s choice of plans, without regard to whether this query is being optimized due to ACS.

I like very much the optimizer group conclusion:

“SPM constrains the optimizer’s choice of plans, without regard to whether this query is being optimized due to ACS

Yes, that’s very correct.  Because ACS and SPM are playing different goals:

ACS is a feature that helps the CBO  adapt itself to the input bind variable in order to generate an execution plan that best fits that bind variable. ACS, given certain conditions, kicks of independently of the presence or not of a SQL Baseline

SPM is a feature which guarantees plan stability and allow plan evolution. It ensures only accepted plan will be executed whatever the technology used by the CBO to generate the best execution plan: ACS or Cardinality Feedback (even thought that Dominic and Kerry Osborne have already investigated the Interaction of SPM and Cardinality Feedback where they both demonstrated that this interaction is not as simple as it looks).

The logic of plan selection when SPM is used follows the following diagram:

SPM Selectin

In which we can see that when the best generated CBO plan is not already inside the SQL plan baseline (i.e. plan is ENABLED and ACCEPETD) then it will not be used. Instead, it will be inserted into the SQL plan history (i.e. ENABLED and not ACCEPTED) waiting to be evolved either manually using DBMS_SPM package or automatically when the Tuning Advisor consent to do so.

What does this means all in all?

In my opinion, in order to have a good collaboration between ACS and SPM, we need to load ACS plans (we have better to do that manually than automatically because they will be immediately ENABLED and ACCEPTED) and hope that all plans generated by the CBO via ACS will match the plans we have already loaded into the SPM baseline.  When the CBO comes up with a plan that is not into the SPM baseline it will not be used. Instead all ENABLED and ACCEPTED plans will compete against each other and the best plan from the Baseline will be selected for use.

The optimizer group example is largely sufficient to explain what I have stated above. The goal of this article is to start from the Coskan’s article end and present a curious observation.

A picture is worth a thousand words (in order to make this post as short as possible, select against dba_sql_plan_baseline will be referred to as pbaseline)

 SQL> > select
  2 to_char(signature) signature
  3 , sql_handle
  4 , plan_name
  5 , enabled
  6 , accepted
  7 from dba_sql_plan_baselines
  8 where signature = 1292784087274697613;

 SIGNATURE           SQL_HANDLE                PLAN_NAME                     ENA  ACC
 ---------------------------------------- ------------------------------ ---------------------------------
 1292784087274697613 SYS_SQL_11f0e4472549338d SQL_PLAN_13w748wknkcwd616acf47 YES YES -> FULL SCAN
 1292784087274697613 SYS_SQL_11f0e4472549338d SQL_PLAN_13w748wknkcwd8576eb1f YES YES -> INDEX RANGE SCAN

 SQL> select sql_id
  2 , child_number
  3 , is_bind_aware
  4 , is_bind_sensitive
  5 , is_shareable
  6 , to_char(exact_matching_signature) sig
  7 , executions
  8 , plan_hash_value
  9 from v$sql
  10 where sql_id = '731b98a8u0knf';

 SQL_ID        CHILD_NUMBER I I I SIG                 EXECUTIONS  PLAN_HASH_VALUE
 ------------- ------------ - - - ------------------- ----------- -----------------
 731b98a8u0knf 0            N Y N 1292784087274697613  2           3625400295
 731b98a8u0knf 1            Y Y Y 1292784087274697613  2           3724264953  -> bind aware
 731b98a8u0knf 2            Y Y N 1292784087274697613  1           3625400295
 731b98a8u0knf 3            Y Y Y 1292784087274697613  1           3625400295  -> bind aware
 

Two plan baselines and two shareable sql child (1 and 3) that are bind sensitive and bind aware so that when FULL bind variable  (‘N1′) is used we get a FULL TABLE SCAN and when INDEX bind variable (‘Y1′) is used we get an INDEX RANGE SCAN.

  •  FULL scan: n=’N1’
 SQL > select count(*), max(col2) from t1 where flag = :n;

 COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
  49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 SQL_ID 731b98a8u0knf, child number 3
 -------------------------------------
 select count(*), max(col2) from t1 where flag = :n

 Plan hash value: 3724264953

 ---------------------------------------------------------------------------
 | Id | Operation        | Name | Rows  | Bytes    | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------
 | 0  | SELECT STATEMENT |      |       |          | 275 (100)  |          |
 | 1  | SORT AGGREGATE   |      | 1     | 30       |            |          |
 |* 2 | TABLE ACCESS FULL| T1   | 49872 | 1461K    | 275 (2)    | 00:00:04 |
 ---------------------------------------------------------------------------

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

 2 - filter("FLAG"=:N)

 Note
 -----
  - SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement
 
  •  INDEX scan : n=’Y1’
 SQL> select count(*), max(col2) from t1 where flag = :n;

 COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
  1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 SQL_ID 731b98a8u0knf, child number 4
 -------------------------------------
 select count(*), max(col2) from t1 where flag = :n

 Plan hash value: 3625400295

 ---------------------------------------------------------------------------------
 | Id | Operation                  | Name | Rows | Bytes | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------------
 | 0  | SELECT STATEMENT           |      |      |       | 2 (100)    |          |
 | 1  | SORT AGGREGATE             |      | 1    | 30    |            |          |
 | 2  | TABLE ACCESS BY INDEX ROWID| T1   | 18   | 540   | 2 (0)      | 00:00:01 |
 |* 3 | INDEX RANGE SCAN           | I1   | 18   |       | 1 (0)      | 00:00:01 |
 -------------------------------------------------------------------------------------

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

 3 - access("FLAG"=:N)

 Note
 -----
  - SQL plan baseline SQL_PLAN_13w748wknkcwd8576eb1f used for this statement
 

ACS produces the  plan which best fits the input bind variable and SPM used that plan because it found it into its  SPM baseline.

 SQL> @pbaseline

 SQL_ID        CHILD_NUMBER I I I SIG                  EXECUTIONS   PLAN_HASH_VALUE
 ------------- ------------ - - - -------------------- ----------- -----------------
 731b98a8u0knf 0            N Y N 1292784087274697613   2          3625400295
 731b98a8u0knf 1            N Y N 1292784087274697613   2          3625400295
 731b98a8u0knf 2            Y Y N 1292784087274697613   1          3625400295
 731b98a8u0knf 3            Y Y Y 1292784087274697613   6          3724264953   -> bind aware
 731b98a8u0knf 4            Y Y N 1292784087274697613   2          3625400295
 731b98a8u0knf 5            Y Y Y 1292784087274697613   1          3625400295   -> bind aware
 

So far so good.

Let’s disturb a little bit this situation by creating an extra index on t1.

 SQL> create index I2 on t1(flag, col2) compress;

 Index created.

SQL> select count(*), max(col2) from t1 where flag = :n;
 COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
  49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 SQL_ID 731b98a8u0knf, child number 3
 -------------------------------------
 select count(*), max(col2) from t1 where flag = :n

 Plan hash value: 3724264953

 ------------------------------------------------------------------------
 | Id | Operation        | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ------------------------------------------------------------------------
 | 0  | SELECT STATEMENT |      |       |       | 275 (100)  |          |
 | 1  | SORT AGGREGATE   |      | 1     | 30    |            |          |
 |* 2 | TABLE ACCESS FULL| T1   | 50110 | 1468K | 275 (2)    | 00:00:04 |
 ------------------------------------------------------------------------

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

 2 - filter("FLAG"=:N)

 Note
 -----
  - SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement
 

That’s nice. The FULL TABLE SCAN baseline (cf47) kicks off appropriately. But let see what plan the CBO comes up with

 SQL > @pbaseline
 SIGNATURE           SQL_HANDLE               PLAN_NAME                     ENA  ACC
 ------------------- ------------------------ --------------------------- ------ ----
 1292784087274697613 SYS_SQL_11f0e4472549338d SQL_PLAN_13w748wknkcwd495f4ddb YES  NO
 1292784087274697613 SYS_SQL_11f0e4472549338d SQL_PLAN_13w748wknkcwd616acf47 YES  YES
 1292784087274697613 SYS_SQL_11f0e4472549338d SQL_PLAN_13w748wknkcwd8576eb1f YES  YES
 

The CBO comes up with a new execution plan (SQL_PLAN_13w748wknkcwd495f4ddb) which has been constrained(discarded) by the SPM baseline. This new plan has been inserted into the SPM plan history (ACCEPTED =’NO’) for future evolution. The newly generated execution plan uses a INDEX FAST FULL SCAN of the new I2 index and it resembles to:

 select * from table(dbms_xplan.display_sql_plan_baseline(plan_name => 'SQL_PLAN_13w748wknkcwd495f4ddb'));
 --------------------------------------------------------------------------------
 SQL handle: SYS_SQL_11f0e4472549338d
 SQL text: select count(*), max(col2) from t1 where flag = :n
 --------------------------------------------------------------------------------

 --------------------------------------------------------------------------------
 Plan name: SQL_PLAN_13w748wknkcwd495f4ddb Plan id: 1230982619
 Enabled: YES Fixed: NO Accepted: NO Origin: AUTO-CAPTURE
 --------------------------------------------------------------------------------

 Plan hash value: 2348726875

 ---------------------------------------------------------------------------
 | Id | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------
 | 0  | SELECT STATEMENT    |      | 1     | 30    | 46 (5)     | 00:00:01 |
 | 1  | SORT AGGREGATE      |      | 1     | 30    |            |          |
 |* 2 | INDEX FAST FULL SCAN| I2   | 25000 | 732K  | 46 (5)     | 00:00:01 |
 ---------------------------------------------------------------------------

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

  2 - filter("FLAG"=:N)
 

But what looks strange it this

 SQL> @pbaseline

 SQL_ID        CHILD_NUMBER I I I SIG                 EXECUTIONS PLAN_HASH_VALUE
 ------------- ------------ - - - ------------------- ----------- ---------------

 731b98a8u0knf 0            N Y N 1292784087274697613  2          3625400295
 731b98a8u0knf 1            N Y N 1292784087274697613  2          3625400295
 731b98a8u0knf 2            Y Y N 1292784087274697613  1          3625400295
 731b98a8u0knf 3            N N Y 1292784087274697613  1          3724264953   -> Shareable but not bind aware
 731b98a8u0knf 4            Y Y N 1292784087274697613  2          3625400295
 

The child cursor number 5 has gone!!! I still have only one shareable child cursor (number 3 the one for FULL TABLE SCAN) which became no bind sensitive and no bind aware. Let’s execute the case of an INDEX RANGE SCAN

 SQL> exec :n := 'Y1';

 SQL> select count(*), max(col2) from t1 where flag = :n;

 COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
  1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 SQL_ID 731b98a8u0knf, child number 3
 -------------------------------------
 select count(*), max(col2) from t1 where flag = :n

 Plan hash value: 3724264953

 ------------------------------------------------------------------------
 | Id | Operation        | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ------------------------------------------------------------------------
 | 0  | SELECT STATEMENT |      |       |       | 275 (100)  |          |
 | 1  | SORT AGGREGATE   |      | 1     | 30    |            |          |
 |* 2 | TABLE ACCESS FULL| T1   | 50110 | 1468K | 275 (2)    | 00:00:04 |
 ------------------------------------------------------------------------

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

 2 - filter("FLAG"=:N)

 Note
 -----
  - SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement

 SQL> @pbaseline
SQL_ID        CHILD_NUMBER I I I SIG                 EXECUTIONS  PLAN_HASH_VALUE
 ------------- ------------ - - - ------------------- ----------- ---------------
 731b98a8u0knf 0            N Y N 1292784087274697613 8           3724264953
 731b98a8u0knf 1            Y Y N 1292784087274697613 2           3625400295
 731b98a8u0knf 2            N Y N 1292784087274697613 6           3625400295
 731b98a8u0knf 3            N N Y 1292784087274697613 15          3724264953 -> Shareable not bind aware
 731b98a8u0knf 4            Y Y N 1292784087274697613 2           3625400295

No way to make a shareable cursor bind sensitive and bind aware in order for the ACS to kick off and generate a plan that is in the SPM baseline.

Don’t tell me that this is due to the new index I2 I have created.

Will you?


SQL> drop index i2;

Index dropped.

SQL> select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
 49999 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

SQL_ID 731b98a8u0knf, child number 3
 -------------------------------------
 select count(*), max(col2) from t1 where flag = :n

Plan hash value: 3724264953

------------------------------------------------------------------------
 | Id | Operation        | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 ------------------------------------------------------------------------
 | 0  | SELECT STATEMENT |      |       |       | 275 (100)  |          |
 | 1  | SORT AGGREGATE   |      | 1     | 30    |            |          |
 |* 2 | TABLE ACCESS FULL| T1   | 49872 | 1461K | 275 (2)    | 00:00:04 |
 ------------------------------------------------------------------------

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

2 - filter("FLAG"=:N)

Note
 -----
 - SQL plan baseline SQL_PLAN_13w748wknkcwd616acf47 used for this statement

SQL> select count(*), max(col2) from t1 where flag = :n;

COUNT(*) MAX(COL2)
 ---------- --------------------------------------------------
 1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

SQL_ID 731b98a8u0knf, child number 5
 -------------------------------------
 select count(*), max(col2) from t1 where flag = :n

Plan hash value: 3625400295

---------------------------------------------------------------------------------
 | Id | Operation                  | Name | Rows | Bytes | Cost (%CPU)| Time     |
 ---------------------------------------------------------------------------------
 | 0  | SELECT STATEMENT           |      |      |       | 2 (100)    |          |
 | 1  | SORT AGGREGATE             |      | 1    | 30    |            |          |
 | 2  | TABLE ACCESS BY INDEX ROWID| T1   | 18   | 540   | 2 (0)      | 00:00:01 |
 |* 3 | INDEX RANGE SCAN           | I1   | 18   |       | 1 (0)      | 00:00:01 |
 --------------------------------------------------------------------------------

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

3 - access("FLAG"=:N)

Note
 -----
 - SQL plan baseline SQL_PLAN_13w748wknkcwd8576eb1f used for this statement

SQL> @pbaseline

SQL_ID        CHILD_NUMBER I I I SIG                  EXECUTIONS  PLAN_HASH_VALUE
 ------------- ------------ - - - -------------------------------  ------------------
 731b98a8u0knf 0            N Y N 1292784087274697613   2          3625400295
 731b98a8u0knf 1            N Y N 1292784087274697613   2          3625400295
 731b98a8u0knf 2            Y Y N 1292784087274697613   1          3625400295
 731b98a8u0knf 3            N Y Y 1292784087274697613   6          3724264953
 731b98a8u0knf 4            Y Y N 1292784087274697613   2          3625400295
 731b98a8u0knf 5            Y Y Y 1292784087274697613   1          3625400295  -> bind aware

When I dropped the index, after a warm up execution my ACS is back.

How could a creation of an extra index disturb the ACS behavior? That’s the subject of my next investigation 

March 21, 2013

ORA-02431: Cannot disable constraint

Filed under: Trouble shooting — hourim @ 7:21 pm

Recently a question came up on the otn forum which reminded me to write a small blog article that I will be referring to instead of creating a different test each time I see people asking how to trouble shoot the same error as that mentioned by the Original Poster(OP). The OP was struggling about an existing constraint which, despite he is seeing it via a select against user_constraints table, he, nevertheless, was unable to disable it because of ORA-02431 error: cannot disable constraint FK_Batch_Products no such constraint.
Here below is the select against the OP user_constraints table:

CONSTRAINT_NAME             CONSTRAINT_TYPE      STATUS
--------------------------- ------------------ -----------
FK_Product_SourceSpecies    R                  ENABLED
FK_Product_CreatePerson     R                  ENABLED
FK_Product_ModifyPerson     R                  ENABLED
FK_Product_ExpressionSystem R                  ENABLED
FK_Product_Localisation     R                  ENABLED
FK_Batch_Products           R                  ENABLED

Have you already spotted the obvious?

Well if not then let me tell you one thing:  each time I see lowercase letters in an Oracle object names then I am hundred percent sure that the owner of this object will have trouble identifying those objects and I will not be surprised when he will be faced to such a kind of non existing object error.

Below I have modeled the problem and have shown the solution to the OP.

SQL> create table t (id number, vc varchar2(10));

Table created.

SQL> alter table t add constraint t_pk primary key (id);

Table altered.

SQL> alter table t add constraint "t_lower_case" check (vc != 'NONE');

Table altered.

SQL> select table_name, constraint_name
  2  from user_constraints
  3  where table_name = 'T';

TABLE_NAME                     CONSTRAINT_NAME
------------------------------ ------------------------------
T                              T_PK
T                              t_lower_case

SQL> alter table t drop constraint t_lower_case;
alter table t drop constraint t_lower_case
                              *
ERROR at line 1:
ORA-02443: Cannot drop constraint  - nonexistent constraint

SQL> alter table t drop constraint "t_lower_case";

Table altered.

Bottom Line : it is very important to be careful when creating Oracle objects; give  them correct naming standard without enclosing their names between double quotes i.e. “ “

Next Page »

Theme: Rubric. Blog at WordPress.com.

Coskan's Approach to Oracle

What I learned about Oracle

So Many Oracle Manuals, So Little Time

“Books to the ceiling, Books to the sky, My pile of books is a mile high. How I love them! How I need them! I'll have a long beard by the time I read them”—Lobel, Arnold. Whiskers and Rhymes. William Morrow & Co, 1988.

EU Careers info

Your career in the European Union

Oracle SQL Tuning Tools and Tips

SQLTXPLAIN (SQLT), TRCANLZR (TRCA), SQL Health-Check (SQLHC) and SQL Tuning Topics

Oracle Scratchpad

Just another Oracle weblog

Tanel Poder's blog: IT & Mobile for Geeks and Pros

Oracle, Exadata, Linux, Performance, Troubleshooting - Mobile Life and Productivity.

OraStory

Dominic Brooks on Oracle Performance, Tuning, Data Quality & Sensible Design ... (Now with added Sets Appeal)

Follow

Get every new post delivered to your Inbox.

Join 31 other followers