Quantcast
Channel: Mohamed Houri’s Oracle Notes
Viewing all 224 articles
Browse latest View live

Index unique scan and the Clustering Factor: does it matter?

$
0
0
When the CBO evaluates the cost of using an index it uses the following formula: The third line of the above formula multiplies the index clustering factor by the effective table selectivity. This means that the more the index clustering factor value is big the less is the chance to see this index used by [...]

Recycle bin: what’s going on?

$
0
0
Have you ever been confronted to such a kind of explain plan? Look carefully to the operation 4 Bizarre!!! From where does this index come from ? More bizarre is this: The Recycle bin is empty while the user_objects table contains 45 objects having a name starting with the BIN characters!!! I will explain how [...]

Interprétation d’un fichier TKPROF

$
0
0
J’ai été destinataire d’un fichier de trace TKPROF correspondant à une requête qui ne s’exécutait pas en un temps acceptable. Le but de cet article est de faire un résumé, étape par étape, de mes investigations relatives à la compréhension de ce problème de performance via l’analyse et l’interprétation de ce fichier TKPROF. Tout d’abord, [...]

2012 in review

$
0
0
Finally, 2012 came to its end. Unsurprisingly my blog has not been as active as I wished it to be. My busiest day was September the 18th in which I have published an article about interpreting TKRPOF which turned to be the most active article in my blog. This gave me a clear indication on [...]

Oracle cached sequences

$
0
0
When dealing with Oracle sequences it is well know that cached sequences values are not lost following a tidy shutdown of a single database instance whereas a brut shutdown will generate a loss of sequences values. I did the experiment under Oracle-Linux and Oracle-Windows and results are shown below: 1.Oracle-Linux Fedora 16 After a normal [...]

SQL PATCH and invisible index

$
0
0
I was playing with the demo presented in the oracle optimizer blog on how to use SQL path to inject a hint into a packaged application and then decided to extend it a little bit to see how this SQL patch will react if I make invisible the index used in the hint. You can [...]

SQL Patch and SQL Plan Baseline how do they collaborate

$
0
0
In my continuing process of investigating SQL Patch and SQL Plan Baseline I wanted to know how these two technologies collaborate together. As I did in my preceding blog article I used the demo proposed by the optimizer group. Remember from the previous post that I have used package dbms_sqldiag_internal.i_create_patch in order to inject an [...]

ORA-02431: Cannot disable constraint

$
0
0
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 [...]

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

$
0
0
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 [...]

What can impeach Adaptive Cursor Sharing kicking off?

$
0
0
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 [...]

Interpreting Execution Plan

$
0
0
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 [...]

SPM baseline selection: how it works?

$
0
0
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 [...]

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

$
0
0

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%';

Jonathan Lewis philosophies : french translation

$
0
0

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’’


SQLTXPLAIN: Execution plan and operation order : Exec Ord column

$
0
0

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



ORA-08102: index key not found: Part II

$
0
0

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.


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

$
0
0

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


Index Coalesce : sys_op_lbid

$
0
0

A recent question on the oracle French forum about rebuilding indexes remembered me to write a small note to show the existence of a more interesting option to use when maintaining an index but unfortunately not widely used. It is the COALESCE index command. Let me put a simple model and explain (a) how to identify indexes that might benefit from the COALESCE and (b) how to show the effect of the COALESCE command on this kind of indexes.

create table t1 as
 select
 trunc(sysdate) + rownum d1,
 rownum n1
 from dual
 connect by level <= 1e6
 ;
create index ind_d1 on t1(d1);

exec dbms_stats.gather_table_stats (user, 't1');

In order to simulate a real life example of an index evolution I will try to update this index several times for the same range of data until it reaches a final stage

begin
 2 for j in 1..100
 3 loop
 4 update t1
 5 set d1 = trunc(d1) + j
 6 where t1.n1 between 15000 and 20000;
 7 end loop;
 8 commit;
 9 end;
 10 /

PL/SQL procedure successfully completed.

begin
 2 for j in 1..100
 3 loop
 4 update t1
 5 set d1 = trunc(d1) - j
 6 where t1.n1 between 15000 and 20000;
 7 end loop;
 8 commit;
 9 end;
 10 /

PL/SQL procedure successfully completed.

begin
 2 for j in 1..100
 3 loop
 4 update t1
 5 set d1 = trunc(d1) - j+1
 6 where t1.n1 between 15000 and 20000;
 7 end loop;
 8 commit;
 9 end;
 10 /

In order to know if my index can benefit from the coalesce command, I will use the sys_op_lbid internal oracle function which gives the number of Keys(leaf block id) per blocks.

select object_name,object_id
from user_objects
where object_name = 'IND_D1';

OBJECT_NAME OBJECT_ID
-------------------- ----------
IND_D1 509423

select
 2 keys_per_leaf, count(*) blocks
 3 from (
 4 select sys_op_lbid (509423, 'L', t1.rowid) block_id,
 5 count (*) keys_per_leaf
 6 from t1
 7 where d1 is not null
 8 group by sys_op_lbid (509423, 'L', t1.rowid)
 9 )
 10 group by keys_per_leaf
 11 order by keys_per_leaf;

KEYS_PER_LEAF BLOCKS
------------- ----------
 2 102
 3 451
 4 352
 5 133
 6 153
 7 72
 8 98
 9 22
 10 63
…..
 134 1
 140 1
 156 1
 196 1
 206 1
 215 1
 292 1
 377 2611
62 rows selected.

This is a smashed index as you might have pointed out that in order to access to 2 leaf block keys we need to visit 102 blocks worth of data and to get 3 other leaf block keys we need to access 451 blocks and so on. This index needs really to be COALESCED. Before coalescing it let me show you how space is spread within this index:

 begin
 p_check_free_space (user, 'IND_D1', 'INDEX');
 end;
 /
Number of Blocks with 0-25% free space = 0 -------> Total Bytes = 0
Number of Blocks with 25-50% free space = 1459 -------> Total Bytes = 11.3984375
Number of Blocks with 50-75% free space = 0 -------> Total Bytes = 0
Number of Blocks with 75-100% free space = 0 -------> Total Bytes = 0
Number of Full Blocks with no free space = 4407 -------> Total Bytes = 34.4296875

Total Blocks : 5866
Total Size MB: 46.928

Now it’s time to coalesce this index and get its new leaf block key distribution

alter index ind_d1 coalesce;

Index altered.

select
 2 keys_per_leaf, count(*) blocks
 3 from (
 4 select sys_op_lbid (509423, 'L', t1.rowid) block_id,
 5 count (*) keys_per_leaf
 6 from t1
 7 where d1 is not null
 8 group by sys_op_lbid (509423, 'L', t1.rowid)
 9 )
 10 group by keys_per_leaf
 11 order by keys_per_leaf;

KEYS_PER_LEAF BLOCKS
------------- ----------
 14 1
 29 1
 35 1
 167 1
 196 1
 210 1
 309 1
 367 1
 377 2649

9 rows selected.

Spot how the index become now (after it has been coalesced) more attractive as far as to get 14 (29, 35 or 367) keys of leaf blocks id we need to visit only one block.

Finally let me show you the new index space configuration the coalesce command has produced

begin
 p_check_free_space (user, 'IND_D1', 'INDEX');
end;
/
Number of Blocks with 0-25% free space = 0 -------> Total Bytes = 0
Number of Blocks with 25-50% free space = 3197 -------> Total Bytes = 24.9765625
Number of Blocks with 50-75% free space = 0 -------> Total Bytes = 0
Number of Blocks with 75-100% free space = 0 -------> Total Bytes = 0
Number of Full Blocks with no free space = 2669 -------> Total Bytes = 20.85

Total Blocks :5866
Total Size MB:46.928

The coalesce command did not changed the size of the index nor the number of its total blocks. However, it did make a nice data distribution since it freed up 1736 new blocks ( 4407-26669) and made 1768 (3197-1429) new blocks offering 25-50% of free space.

Before closing this blog article I would like to emphasize that if you arrive to the conclusion that your index needs to be coalesced then think about a periodic (each week for example) coalesce of this index. Because the conditions which smashed your index (delete of old data from left hand side of the index and insert new data into the right hand side of the index) is still present and will sooner or later smash your index so that a new coalesce will be necessary.


Indexed virtual column and DML error logging : ORA-03113: end-of-file on communication channel

$
0
0

This is a brief note to show you that when combining virtual column and DML error logging you might be in trouble as I have been.  First the model


create table t1(n1 number, dat1 timestamp(6));

alter table t1 add virt_n1 generated always as (case when dat1 is null then n1 else null end) virtual;

create index ind_virt_n1 on t1(virt_n1);

create table t2(n1 number, n2 number);

insert into t1 (N1,DAT1) values (1067597,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (1067597,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (1067597,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (1067597,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (1067597,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (36869,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (36869,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (170012,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (170012,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (170012,to_timestamp('05-AUG-13 10.44.09.456703000 AM','DD-MON-RR HH.MI.SS.FF AM'));
insert into t1 (N1,DAT1) values (170012,null);

insert into t2 select distinct n1, n1+1 from t1;

commit;

exec dbms_errlog.create_error_log (dml_table_name => 't1');

I have created two tables, t1 and t2.  I enriched t1 with a virtual column (virt_n1) and a non unique index (ind_virt_n1) on this virtual column. And finally, I create a DML error logging table (err$_t1) in order to log rejected records when inserting into t1;

And now the problem

insert into t1
    (n1
    ,dat1)
select
   t2.n1
   ,systimestamp
from t2
log errors into ERR$_t1 reject limit unlimited;
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

It took me a couple of minutes to figure out that this error is due the DML logging error because when I get rid of this error logging the insert works perfectly

insert into t1
(n1
,dat1)
select  t2.n1
,systimestamp
from t2;

3 rows created.

But wait; I was not going to let down my DML error just because of this end-of-file on communication channel?  I was curious to know the reason that impeaches my insert to work when combined with the DML error logging. The way I did approached this issue is to ask myself what is the difference between the actual situation and the old and many situations where I did smoothly used the DML error logging. You might have already guessed the answer because it is in the title of this blog article: virtual column. So, I immediately described the err$_t1 which shows the presence of the virtual column


SQL> desc err$_t1
Name                            Null?    Type
------------------------------- -------- ---------------
1      ORA_ERR_NUMBER$                          NUMBER
2      ORA_ERR_MESG$                            VARCHAR2(2000)
3      ORA_ERR_ROWID$                           ROWID
4      ORA_ERR_OPTYP$                           VARCHAR2(2)
5      ORA_ERR_TAG$                             VARCHAR2(2000)
6      N1                                       VARCHAR2(4000)
7      DAT1                                     VARCHAR2(4000)
8      VIRT_N1                                  VARCHAR2(4000) -- virtual column

By simply dropping the virtual column from the err$_t1 the insert works perfectly  in the presence of DML error logging table as shown below:

alter table err$_t1 drop column virt_n1;
Table altered.

insert into t1
(n1
,dat1)
select  t2.n1
,systimestamp
from t2
log errors into ERR$_t1 reject limit unlimited;

3 rows created.

I was still curious to know why the presence of a virtual column in the error table impeaches things to work correctly. There might be another reason. This is why I started as an electrician who is having an electrical problem in his installation but doesn’t know exactly which is the culprit electrical devise is.  I modeled my real life tables and started dropping object by object (foreign key, unique key, index) until I found the culprit object:  ind_virt_n1 the index on the virtual column.

If you re-create the model presented above and, this time, drop the index ind_virt_n1 and let the virtual column in the err$_t1 table the insert will this time work perfectly as shown below:

drop index ind_virt_n1;
Index dropped.

insert into t1
    (n1
    ,dat1)
select
    t2.n1
   ,systimestamp
from t2
log errors into ERR$_t1 reject limit unlimited;

3 rows created.

The bottom line of this article is to show that mixing indexed virtual column and DML error logging might not work without error. In my case I opted for dropping the virtual column from the err$_t1 instead of dropping the virtual index because of the performance gain this index brings to my application.


INDEX FULL SCAN , NLS_SORT and ORDER BY

$
0
0

If I was to ask you how an INDEX FULL SCAN is operated by the SQL engine you would certainly answer that it will go to the first leaf block of the index and walk down up to the last leaf block in the index key order using typically a db file sequential read.

I have strengthened the words  index key order because it is specifically that operation which interests me here in this article. Each time I see an INDEX FULL SCAN operation in an execution plan, I immediately try to know if the CBO did took an advantage of this typical access to avoid an eventual supplementary ORDER BY operation. There is a bug, under the FIRST_ROWS mode, where an INDEX FULL SCAN is preferred, whatever its cost is, in order to avoid an ORDER BY operation. Hence, in the presence of such index operation, I also try to verify the CBO mode and/or the presence of a where clause such as where rownum=1 which makes, behind the scene, the CBO behaving as if it was running under FIRST_ROWS mode.

Recently an excellent question comes up in a French forum where the Original Poster (OP) was wondering why the CBO was making a wrong decision. Several very good interventions by very nice peoples motivated me to write two articles, the first one related to the relationship that might exist between an INDEX FULL SCAN and an ORDER BY operation while the second article will look on the effect the optimizer_index_cost_adj parameter might have on the choice of a good or wrong execution path.

The  OP query and execution plan  are shown below:

SELECT
     colonne1
FROM matable
GROUP BY colonne1
ORDER BY colonne1 ASC NULLS LAST;
Plan hash value: 2815412565

------------------------------------------------------------------------------------------
| Id  | Operation         | Name                 | Starts | E-Rows | A-Rows |   A-Time   |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                      |      1 |        |      3 |00:00:21.60 |
|   1 |  SORT ORDER BY    |                      |      1 |      3 |      3 |00:00:21.60 |
|   2 |   HASH GROUP BY   |                      |      1 |      3 |      3 |00:00:21.60 |
|   3 |    INDEX FULL SCAN| MATABLE_PK           |      1 |   2923K|   2928K|00:00:21.99 |
------------------------------------------------------------------------------------------

The query takes more than 20 seconds to complete. And when he instructs the CBO to use a FULL table scans the response time fells down to about 4 seconds

SELECT /*+ NO_INDEX(matable matable_pk) */
colonne1
FROM matable
GROUP BY colonne1
ORDER BY colonne1 ASC NULLS LAST;

-----------------------------------------------------------------------------------------
| Id  | Operation           | Name              | Starts | E-Rows | A-Rows |   A-Time   |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |                   |      1 |        |      3 |00:00:04.03 |
|   1 |  SORT ORDER BY      |                   |      1 |      3 |      3 |00:00:04.03 |
|   2 |   HASH GROUP BY     |                   |      1 |      3 |      3 |00:00:04.03 |
|   3 |    TABLE ACCESS FULL| MATABLE           |      1 |   2923K|   2928K|00:00:03.19 |
-----------------------------------------------------------------------------------------

The next blog article will discuss the reason of this wrong execution plan choice.  For this moment, let me just spot with you the duplicate sort operation the OP has got.

------------------------------------------------------------------------------------------
| Id  | Operation         | Name                 | Starts | E-Rows | A-Rows |   A-Time   |
------------------------------------------------------------------------------------------
|   1 |  SORT ORDER BY    |                      |      1 |      3 |      3 |00:00:21.60 |
|   2 |   HASH GROUP BY   |                      |      1 |      3 |      3 |00:00:21.60 |
|   3 |    INDEX FULL SCAN| MATABLE_PK           |      1 |   2923K|   2928K|00:00:21.99 |
------------------------------------------------------------------------------------------

An ordered INDEX FULL SCAN (on the leading PK column) access followed by a SORT ORDER BY of this PK column.

Why?

This is the aim of the current blog article.

First let me present the model

CREATE TABLE t
(c1 VARCHAR2(64), c2 CHAR(15), d1 DATE);

INSERT INTO t
  SELECT
       mod(ABS(dbms_random.random),3)+ 1||chr(ascii('Y')) ,
       dbms_random.string('L',dbms_random.value(1,5))||rownum ,
       to_date(TO_CHAR(to_date('01/01/1980','dd/mm/yyyy'),'J') + TRUNC(dbms_random.value(1,11280)),'J')
FROM dual
CONNECT BY level <= 2e6;

ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (c1,c2) USING INDEX;

EXEC dbms_stats.gather_table_stats (USER, 't', CASCADE => true, method_opt => 'FOR ALL COLUMNS SIZE 1');

And now the query on 11.2.0.3.0 – 64bit Production

SQL > SELECT  c1
 2    FROM t
 3    GROUP BY c1
 4   ORDER BY c1 ASC NULLS LAST;

C1
 --------------------------
 1Y
 2Y
 3Y

--------------------------------------
 SQL_ID  0nfhzk4r58zuw, child number 1
 -------------------------------------
 SELECT  c1   FROM t   GROUP BY c1  ORDER BY c1 ASC NULLS LAST

Plan hash value: 2111031280
 -----------------------------------------------------------------------------
 | Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
 -----------------------------------------------------------------------------
 |   0 | SELECT STATEMENT     |      |       |       |  2069 (100)|          |
 |   1 |  SORT GROUP BY NOSORT|      |     3 |     9 |  2069   (5)| 00:00:06 |
 |   2 |   INDEX FULL SCAN    | T_PK |  2000K|  5859K|  2069   (5)| 00:00:06 |
 -----------------------------------------------------------------------------

As I have expected, an ordered INDEX FULL SCAN on the leading primary key column which allows the CBO to avoid the ORDER BY c1 operation as clearly shown by the operation 1 SORT GROUP BY NOSORT

So what is the difference between my model and the OP one? Or more precisely what is the difference between my environment and the OP one? It should exist something that makes the difference. Fortunately the thread was under good hands and someone cleverly asked to get the execution plan with the advanced option thought that his intention was to see the cost. Nevertheless, the advanced option shows that the OP was using a French NLS_SORT parameter.

Hmmmm…

Let me then change my nls_sort to FRENCH and see what happens to my engineered query


SQL> show parameter nls_sort

NAME                                 TYPE        VALUE
------------------------------------ ----------- ----------
nls_sort                             string      BINARY

SQL> alter session set nls_sort=FRENCH;

Session altered.

SQL> SELECT  c1
2    FROM t
3    GROUP BY c1
4   ORDER BY c1 ASC NULLS LAST;

C1
------------------------
1Y
2Y
3Y

SQL_ID  0nfhzk4r58zuw, child number 3
-------------------------------------
SELECT  c1   FROM t   GROUP BY c1  ORDER BY c1 ASC NULLS LAST

Plan hash value: 1760210272

------------------------------------------------------------------------------
| Id  | Operation             | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |       |       |  2451 (100)|          |
|   1 |  SORT ORDER BY        |      |     3 |     9 |  2451  (20)| 00:00:07 |
|   2 |   SORT GROUP BY NOSORT|      |     3 |     9 |  2451  (20)| 00:00:07 |
|   3 |    INDEX FULL SCAN    | T_PK |  2000K|  5859K|  2069   (5)| 00:00:06 |
------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
3 - SEL$1 / T@SEL$1

Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.3')
DB_VERSION('11.2.0.3')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "T"@"SEL$1" ("T"."C1" "T"."C2"))
END_OUTLINE_DATA
*/

Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=1) NLSSORT("C1",'nls_sort=''FRENCH''')[2000],"C1"[VARCHAR2,256]
2 - (#keys=1) "C1"[VARCHAR2,256]
3 - "C1"[VARCHAR2,256] 

The column projection gives an interesting information on what’s going on here (nls_sort= french)


Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=1) NLSSORT("C1",'nls_sort=''FRENCH''')[2000],"C1"[VARCHAR2,256]

On contrast to the situation where my column c1 would have been declared as of a NUMBER data type, the nls_sort parameter value would not have played any effect as shown below:


SQL> describe t1

Name                            Null?    Type
------------------------------- -------- ----------
1      C1                              NOT NULL NUMBER
2      C2                              NOT NULL CHAR(15)
3      D1                                       DATE

SQL> show parameter nls_sort

NAME                                 TYPE        VALUE
------------------------------------ ----------- -----------
nls_sort                             string      FRENCH

SQL>SELECT  c1
2    FROM t1
3    GROUP BY c1
4   ORDER BY c1 ASC NULLS LAST;

C1
----------
1
2
3

------------------------------------------------------------------------------
| Id  | Operation            | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |       |       |       |  2105 (100)|          |
|   1 |  SORT GROUP BY NOSORT|       |  1754K|    21M|  2105   (5)| 00:00:06 |
|   2 |   INDEX FULL SCAN    | T1_PK |  1754K|    21M|  2105   (5)| 00:00:06 |
------------------------------------------------------------------------------

SQL> alter session set nls_sort = BINARY;

Session altered.

SQL> SELECT  c1
2    FROM t1
3    GROUP BY c1
4   ORDER BY c1 ASC NULLS LAST;

C1
----------
1
2
3

------------------------------------------------------------------------------
| Id  | Operation            | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |       |       |       |  2105 (100)|          |
|   1 |  SORT GROUP BY NOSORT|       |  1754K|    21M|  2105   (5)| 00:00:06 |
|   2 |   INDEX FULL SCAN    | T1_PK |  1754K|    21M|  2105   (5)| 00:00:06 |
------------------------------------------------------------------------------

Footnote: When you see in your execution plan two ordered operations like and INDEX FULL SCAN followed by an ORDER BY on the leading index column then check the nls_sort parameter. It might be due to the difference of the session nls_sort parameter and the sort parameter used internally by Oracle when reading the INDEX FULL SCAN keys.


Viewing all 224 articles
Browse latest View live


Latest Images