Oracle Scratchpad

March 9, 2008

Cursor_sharing

Filed under: CBO, Execution plans, Hints, Troubleshooting — Jonathan Lewis @ 9:13 pm UTC Mar 9,2008

From time to time I’ve commented on the fact that setting cursor_sharing to force or similar may be a temporary workaround to bad coding practises, but that it can introduce problems, has a few associated bugs, and shouldn’t  be viewed as a guaranteed, or long-term, solution.


I don’t think I’ve ever published any specific examples, though, of the oddities that can appear when you enable the feature – so here’s a simple one to watch out for:


select
	{list of columns}
from	{table}
where	rownum = 1
;

What difference will setting cursor_sharing to force make for this query?

Here are the “before” and “after” execution plans in 10.2.0.3 (pulled out of memory using the dbms_xplan.display_cursor() procedure) for an example of this type of query. :


SQL_ID  16x8y4zkwqhtt, child number 0
-------------------------------------
select  /*+ not sharing */  small_vc from t1 where rownum = 1        

Plan hash value: 3836375644
-----------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost  |
-----------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |     2 |
|*  1 |  COUNT STOPKEY     |      |       |       |       |
|   2 |   TABLE ACCESS FULL| T1   |     1 |    11 |     2 |
-----------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM=1)
.
.
.
SQL_ID  56rbgjy8h1yaa, child number 0
-------------------------------------
select  /*+ sharing */  small_vc from t1 where rownum = :"SYS_B_0"        

Plan hash value: 624922415
------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost  |
------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |       |       |    16 |
|   1 |  COUNT              |      |       |       |       |
|*  2 |   FILTER            |      |       |       |       |
|   3 |    TABLE ACCESS FULL| T1   |  3000 | 33000 |    16 |
------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(ROWNUM=:SYS_B_0)        

The question, of course, is how much difference that extra filter operation at line 2 makes to the performance. If you can’t guess from the plan, you can always re-run the queries with autotrace enabled to capture the execution statistics:


Statistics (cursor_sharing = exact)
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        419  bytes sent via SQL*Net to client
        396  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
.
.
.
Statistics (cursor_sharing = force)
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         97  consistent gets
          0  physical reads
          0  redo size
        419  bytes sent via SQL*Net to client
        396  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed       

In the first run the optimizer could see that it had to stop after one row of the tablescan. But when the rownum target was forced into a bind variable, the optimizer produced a plan that made the entire tablescan happen with a check on every single row returned to see if its row number matched the input bind variable!

This anomaly is fixed in 11g – the filter operation disappears – but if you’ve got code like this in any earlier versions of Oracle, then it needs to be fixed. Funnily enough, changing the predicate from “rownum = 1″ to “rownum <= 1″ is sufficient to allow Oracle to get the right answer with the minimum of work.

There are a couple of other common anomalies that appear when you enable the feature, and I may find time to write about them in the future. In the interim you might want to investigate adding the hint /*+ cursor_sharing_exact */ (which appeared in 9i) to any queries that behave sufficiently badly when you fiddle with the cursor_sharing parameter – this gives you a localised way of turning sharing off.

4 Comments »

  1. I almost got bitten by
    select *
    from parttab
    where col like :a1 escape :a2
    and partkey=:a3

    while having a locally partitioned (list) index on col. Oracle 9i did a range scan, 10g would do a full scan unless either a2 or a3 was a literal, or the index was global. Since the app has a middle layer (vendor independence, ugh) that does its own cursor_sharing=force, I had to un-partition my indexes before migrating to 10g. I haven’t got around to investigating the issue yet, but it seems to be something to watch out for.

    Comment by Flado — March 9, 2008 @ 10:06 pm UTC Mar 9,2008 | Reply

  2. How come the Outline information are the same for both queries? Since the execution plans are different, shouldn’t their Outline information be different too?

    I used the dbms_xplan.display_cursor with the ‘advanced’ format as per your March 6, 2008’s blog entry on Dbms_Xplan(3)

    SQL> Select *
    From Table(Dbms_Xplan.Display_Cursor(’g2u25sckmws3b’,Null,’Advanced’));
    2 SQL_ID g2u25sckmws3b, child number 0
    ————————————-
    select /*+ tst */ pmc_cd, account_num from account where rownum = 1

    Plan hash value: 3971388088

    ——————————————————————————
    | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    ——————————————————————————
    | 0 | SELECT STATEMENT | | | | 2 (100)| |
    |* 1 | COUNT STOPKEY | | | | | |
    | 2 | TABLE ACCESS FULL| ACCOUNT | 1 | 20 | 2 (0)| 00:00:01 |
    ——————————————————————————

    Query Block Name / Object Alias (identified by operation id):
    ————————————————————-

    1 – SEL$1
    2 – SEL$1 / ACCOUNT@SEL$1

    Outline Data
    ————-

    /*+
    BEGIN_OUTLINE_DATA
    IGNORE_OPTIM_EMBEDDED_HINTS
    OPTIMIZER_FEATURES_ENABLE(’10.2.0.3′)
    OPT_PARAM(’optimizer_index_cost_adj’ 10)
    OPT_PARAM(’optimizer_index_caching’ 90)
    OUTLINE_LEAF(@”SEL$1″)
    FULL(@”SEL$1″ “ACCOUNT”@”SEL$1″)
    END_OUTLINE_DATA
    */

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

    1 – filter(ROWNUM=1)

    Column Projection Information (identified by operation id):
    ———————————————————–

    1 – “PMC_CD”[VARCHAR2,64], “ACCOUNT_NUM”[VARCHAR2,17]
    2 – “PMC_CD”[VARCHAR2,64], “ACCOUNT_NUM”[VARCHAR2,17]

    ——————————————–

    SQL> Select *
    From Table(Dbms_Xplan.Display_Cursor(’a88w7tnav8dvc’,Null,’Advanced’));
    2 SQL_ID a88w7tnav8dvc, child number 0
    ————————————-
    select /*+ tst_bind */ pmc_cd, account_num from account where rownum
    = :bind_val

    Plan hash value: 4109293921

    ——————————————————————————-
    | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    ——————————————————————————-
    | 0 | SELECT STATEMENT | | | | 8396 (100)| |
    | 1 | COUNT | | | | | |
    |* 2 | FILTER | | | | | |
    | 3 | TABLE ACCESS FULL| ACCOUNT | 4027K| 76M| 8396 (2)| 00:01:41 |
    ——————————————————————————-

    Query Block Name / Object Alias (identified by operation id):
    ————————————————————-

    1 – SEL$1
    3 – SEL$1 / ACCOUNT@SEL$1

    Outline Data
    ————-

    /*+
    BEGIN_OUTLINE_DATA
    IGNORE_OPTIM_EMBEDDED_HINTS
    OPTIMIZER_FEATURES_ENABLE(’10.2.0.3′)
    OPT_PARAM(’optimizer_index_cost_adj’ 10)
    OPT_PARAM(’optimizer_index_caching’ 90)
    OUTLINE_LEAF(@”SEL$1″)
    FULL(@”SEL$1″ “ACCOUNT”@”SEL$1″)
    END_OUTLINE_DATA
    */

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

    2 – filter(ROWNUM=:BIND_VAL)

    Column Projection Information (identified by operation id):
    ———————————————————–

    1 – “PMC_CD”[VARCHAR2,64], “ACCOUNT_NUM”[VARCHAR2,17]
    2 – “PMC_CD”[VARCHAR2,64], “ACCOUNT_NUM”[VARCHAR2,17]
    3 – “PMC_CD”[VARCHAR2,64], “ACCOUNT_NUM”[VARCHAR2,17]

    Comment by Milo — March 10, 2008 @ 11:38 pm UTC Mar 10,2008 | Reply

  3. Milo,

    Interesting point – I guess the answer is that (a) there is no hint that is the equivalent of “you must filter at this point” and (b) the table order, access methods, join methods, and cardinality arithmetic don’t need to be manipulated for the filter to be relevent – so there is no need to have a directive to filter, it just happens anyway.

    Comment by Jonathan Lewis — March 11, 2008 @ 6:02 am UTC Mar 11,2008 | Reply

  4. Flado,

    Thanks for passing on that warning.

    Comment by Jonathan Lewis — March 11, 2008 @ 6:11 am UTC Mar 11,2008 | Reply


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.