Got a question for the Optimizer team?

If you have an idea for a post you would like to see on our blog please email us

Wednesday, October 28, 2009

Oracle Open World 2009 Summary

We had a great time talking to our users at Open World 2009 both at our Demogrounds booth and at our two sessions. We received a lot of interesting questions during the Optimizer Roundtable discussion, but we did not get to answer all of them due to time constraints. We plan to address the questions we received (both answered and unanswered) in future blog posts... so stay tuned. If you didn't get to attend the discussion, but have a question about the Optimizer, submit it through the email link above.

For those of you who did not get a chance to stop by our Demogrounds booth, here's a recap of the new features that we talked about. Many of the topics have already been covered in earlier blog posts.
These topics are focused on well-known pain points from earlier versions of Oracle. But we also have plenty of new optimizations in Oracle 11gR1 and 11gR2. Stay tuned for details about some of our new optimizations.

Friday, October 9, 2009

Open World Recap and New White papers

The Optimizer group has two session and a demo station in the Database campground at this year's Oracle Open World. We will give a technical presentation on What to Expect from the Oracle Optimizer When Upgrading to Oracle Database 11g and host an Oracle Optimizer Roundtable.

The technical session, which is on Tuesday Oct 13 at 2:30 pm, gives step by step instructions on how to use the new 11g features to ensure your upgrade goes smoothly and without any SQL plan regressions. This session is based on our latest white papers, Upgrading from Oracle Database 10g to 11g: What to expect from the Optimizer and SQL Plan Management in Oracle Database 11g.

The roundtable, which is on Thursday Oct. 15th at 10:30 am, will give you a first hand opportunity to pose you burning Optimizer and statistics questions directly to a panel of our leading Optimizer developers. In fact if you plan to attend the roundtable and already know what questions you would like to ask, then please send them to us via email and we will be sure to include them. Other wise, you can hand in your questions at our demo station at any stage during the week, or as you enter the actual session. Just be sure to write your questions in clear block capitals!

We look forward to see you all at Open world.

Tuesday, September 1, 2009

What's Changed between my New Query Plan and the Old One?

In most cases the first step in debugging a performance problem caused by a
plan change is to visually inspect both of the execution plans generated by the query optimizer. Usually the customer has a known plan that performed well and the
new plan that performs worse.

Visual inspection of plans is easy when the query is not too complex but becomes a tedious exercise when the query is complex (involving tens of joins, sub-queries, views, etc).

This article introduces a new plan comparison tool implemented in Oracle Database 11gR2.

1. Comparing Complex Plans

Plan comparison is becoming a more critical issue for two key reasons:
  • The majority of SQL queries are now generated by applications and they are becoming larger and more complex.

  • The optimizer has kept pace with the complexity of the queries by
    implementing newer and smarter algorithms to rearrange the user's queries.

In the increasingly complex optimizer world, entire tables can get eliminated from the plan, views can get merged, sub-queries can get unnested, expressions can be converted to virtual columns, complete predicates can be eliminated, new join methods can be introduced, and so on. Therefore, the query execution plan can potentially look radically different from the user's queries.

With increasing query complexity and query optimization, one thing has not
changed - developers, support engineers, DBAs, and consultants still visually inspect plans to figure out why one is different from the other. Worse case, they may have to look at the raw dump files from 10053 traces side by side.

2. Plan Comparison in Oracle Database 11g

The Plan Comparison (Plan Diff) in Oracle Database 11gR2 addresses the issue of comparing complex plans. It uses optimizer kernel level information
to come up with the key reasons why one plan is different from the other.

Consider this simple query that joins the Sales and Customers tables.
Example 1

SELECT s.prod_id
FROM sales s, customers c
WHERE s.cust_id = c.cust_id

The 11g plan for the query is shown below.

Plan Hash Value  : 2489314924
-----------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
-----------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 960 | 4800 | 5 | 00:00:01 |
| 1 | PARTITION RANGE ALL | | 960 | 4800 | 5 | 00:00:01 |
| 2 | TABLE ACCESS FULL | SALES | 960 | 4800 | 5 | 00:00:01 |
-----------------------------------------------------------------------

A cursory look at the plan tells us that the Customers table has vanished from
the query. This means that the optimizer has optimized the query to a
point where the customers table has been deemed redundant.

Now, lets look at the 10g plan:

Plan Hash Value  : 2774269550
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 960 | 14400 | 8 | 00:00:01 |
| * 1 | HASH JOIN | | 960 | 14400 | 8 | 00:00:01 |
| 2 | INDEX FAST FULL SCAN | CUSTOMERS_PK | 630 | 3150 | 2 | 00:00:01 |
| 3 | PARTITION RANGE ALL | | 960 | 9600 | 5 | 00:00:01 |
| 4 | TABLE ACCESS FULL | SALES | 960 | 9600 | 5 | 00:00:01 |
----------------------------------------------------------------------------------

The 10g plan is more conventional - all the tables in the users query are
still present.

Typically, the first step in comparing two plans would be to look at the row
sources. But, this won't yield any more information for us, in this case, because the entire Customers table has vanished in the 11g plan. Without Plan Comparison, we would have to look into the optimizer trace. Deep in the trace file, we would find a line telling us that JOIN ELIMINATION was successful in 11g.

By using Plan Comparison, we can get the same information by calling one of the new functions in the well known DBMS_XPLAN package.


3. Invoking Plan Comparison APIs

In the example below, we will use the dbms_xplan.diff_plan_outline function to compare our two plans. The function takes three arguments,
  • SQL Statement text

  • Hints to reproduce the first plan

  • Hints to reproduce the second plan


In our example we only need to specify one hint to reproduce each plan, OPTIMIZER_FEATURES_ENABLE. By setting it to 11.1.0.7 and 10.1.0.3 we will be able to reproduce each plan.

Step #1: Create a package to house our SQL statement

connect / as sysdba
grant connect, dba, advisor, resource, query rewrite to sh;

connect sh/sh;

SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 1000
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 8000

set serveroutput on
SET LONG 20000
SET DEFINE OFF

-- setup the query text
create or replace package define_vars is
sql_stmt varchar2(2000) := q'#
select s.prod_id
from sales s, customers c
where s.cust_id = c.cust_id
#';
end;
/

PL/SQL procedure successfully completed.


Step #2: Run the Diff_Plan_Outline function to generate a report


DECLARE
t_id varchar2(50);
BEGIN
t_id := dbms_xplan.diff_plan_outline(
sql_text => define_vars.sql_stmt,
outline1 => 'OPTIMIZER_FEATURES_ENABLE(''11.1.0.7'')',
outline2 => 'OPTIMIZER_FEATURES_ENABLE(''10.1.0.3'')');
END;
/


And you should see something like the following as output:

URL:
http://host.my.com:portnumber/orarep/plandiff/all?task_id=31&format=html&method=qbreg

PL/SQL procedure successfully completed.

Note the task_id number as it will be used in the next API to fetch the
comparison report. In this case the task_id=31

Step #3: Run the DBMS_REPORT.Get_Report function to generate the report.

You now can retrieve and display the report in TEXT format by changing the '&format=html' output you got above to '&format=text'.

set define off;
set long 1000000;

select dbms_report.get_report(
'/orarep/plandiff/all?task_id=31&format=text&method=qbreg')
from dual;

Remember to replace the task_id value with the one you see after running the report
generating task above.

4. The Plan Diff Report

The first part of the report tells you the first points of deviation between
the two plans at a Query Block level. In our example, the report tells
us that 'JOIN REMOVED FROM QUERY BLOCK' is the difference between two plans.

In more complicated plans, there might be more than one points of deviation.
Each of those points will be show in the first part of the report.

After the Query Block level Diff, the report contains the Join Order Diff.
In this sample query, there is no join order diff so this section is empty.


Report Details: SQL Plan Comparison Query Block Diff
------------------------------------------------------------------------
| Query Block | Plan 1 | Plan 2 | Diff |
------------------------------------------------------------------------
| .. SEL$5B7CE5FC | Yes | NA | .. JOIN REMOVED FROM QUERY BLOCK |
------------------------------------------------------------------------


Let's look at two more examples to help illustrate the other areas of the report that can be of interest when comparing two plans.

Example2: Plan Diff with Join Order difference

 select  count(*)
from products, costs
where products.prod_list_price = costs.unit_cost;

Again this query is a simple two table join but there are two possible join orders for this statement. By using plan plan_diff we can compare the two alternative plans.

Step #1: Create a package to house our SQL statement

SQL> create or replace package define_vars is
2 sql_stmt varchar2(2000) := q'#
3 select count(*)
4 from products, costs
5 where products.prod_list_price = costs.unit_cost
6 #';
7 end;
8 /

Package created.


Step #2: Run the Diff_Plan_Outline function to generate a report. Again in this case we will pass in two hints to generate the plans with both join orders.


SQL> DECLARE
2 t_id varchar2(50);
3 BEGIN
4 t_id := dbms_xplan.diff_plan_outline(sql_text =>define_vars.sql_stmt,
5 outline1 => 'LEADING(@"SEL$1" "COSTS"@"SEL$1" "PRODUCTS"@"SEL$1")',
6 outline2 => 'LEADING(@"SEL$1" "PRODUCTS"@"SEL$1" "COSTS"@"SEL$1")');
7 end;
8 /

Step #3: Run the DBMS_REPORT.Get_Report function to generate the report.

set define off;
set long 1000000;

select dbms_report.get_report(
'/orarep/plandiff/all?task_id=64&format=text&method=qbreg')
from dual;


At the start of the report is the Query Block level Diff section, followed by the Join Order Diff section. For our example the output of the Join Order Diff section is as follows



Join Order Diff
-------------------------------------------------
| Query Block | SQL Plan 1 | SQL Plan 2 |
-------------------------------------------------
| SEL$1 | COSTS,PRODUCTS | PRODUCTS,COSTS |
-------------------------------------------------

Plan Hash Value : 3848794364
--------------------------------------------
| Id | Operation | Name |
--------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT AGGREGATE | |
| * 2 | HASH JOIN | |
| 3 | PARTITION RANGE ALL | |
| 4 | TABLE ACCESS FULL | COSTS |
| 5 | TABLE ACCESS FULL | PRODUCTS |
--------------------------------------------

Plan Hash Value : 1193021615
--------------------------------------------
| Id | Operation |Name |
--------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT AGGREGATE | |
| * 2 | HASH JOIN | |
| 3 | TABLE ACCESS FULL | PRODUCTS |
| 4 | PARTITION RANGE ALL | |
| 5 | TABLE ACCESS FULL | COSTS |
--------------------------------------------


It shows the different orders in a summary table followed by the two different plans.

Example 3: Plan Diff with different Join Methods

select /*+ LEADING(@"SEL$1" "COSTS"@"SEL$1" "PRODUCTS"@"SEL$1") */ count(*)
from products, costs
where products.prod_list_price = costs.unit_cost;

In this example we use the same query again except this time we have added a LEADING hint to force a particular join order. We will now use plan diff to look at the two possible join types for this statement.
Step #1: Create a package to house our SQL statement

SQL> create or replace package define_vars is
2 sql_stmt varchar2(2000) := q'#
3 select /*+ LEADING(@"SEL$1" "COSTS"@"SEL$1" "PRODUCTS"@"SEL$1") */ count(*)
4 from products, costs
5 where products.prod_list_price = costs.unit_cost
6 #';
7 end;
8 /

Package created.


Step #2: Run the Diff_Plan_Outline function to generate a report

SQL>DECLARE
2 t_id varchar2(50);
3 BEGIN
4 t_id := dbms_xplan.diff_plan_outline(sql_text => define_vars.sql_stmt,
5 outline1 => 'USE_MERGE(products)',
6 outline2 => 'USE_HASH(@"SEL$1" "PRODUCTS"@"SEL$1")');
7 end;
8 /

Step #3: Run the DBMS_REPORT.Get_Report function to generate the report.

set define off;
set long 1000000;

select dbms_report.get_report(
'/orarep/plandiff/all?task_id=245&format=text&method=qbreg')
from dual;


This time in the report the first two sections of the report are empty but the third section called Join Method Diff is populated (indicating this is where the difference occurs. It shows the two different join methods used and their corresponding execution plans.



Task Information: Workload Information:
------------------------------------- -------------------------------------
Task Name : TASK_245
Task Owner : SH
Description : diff_plan_outline

Report Details: SQL Plan Comparison Query Block Diff
------------------------------------------------
| Query Block | SQL Plan 1 | SQL Plan 2 | Diff |
------------------------------------------------

Join Order Diff
-----------------------------------------
| Query Block | SQL Plan 1 | SQL Plan 2 |
-----------------------------------------

Join Method Diff
--------------------------------------------------------------
| Query Block | Table to Join | SQL Plan 1 | SQL Plan 2
|
--------------------------------------------------------------
| SEL$1 | "PRODUCTS"@"SEL$1" | MERGE | HASH |
--------------------------------------------------------------

Plan Hash Value : 1572665727
---------------------------------------------
| Id | Operation | Name |
---------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT AGGREGATE | |
| 2 | MERGE JOIN | |
| 3 | SORT JOIN | |
| 4 | PARTITION RANGE ALL | |
| 5 | TABLE ACCESS FULL | COSTS |
| * 6 | SORT JOIN | |
| 7 | TABLE ACCESS FULL | PRODUCTS |
---------------------------------------------

Plan Hash Value : 3848794364
--------------------------------------------
| Id | Operation | Name |
--------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT AGGREGATE | |
| * 2 | HASH JOIN | |
| 3 | PARTITION RANGE ALL | |
| 4 | TABLE ACCESS FULL | COSTS |
| 5 | TABLE ACCESS FULL | PRODUCTS |
--------------------------------------------

Tuesday, August 11, 2009

Understanding DBMS_STATS.SET_*_PREFS procedures

In previous Database releases you had to use the DBMS_STATS.SET_PARM procedure to change the default value for the parameters used by the DBMS_STATS.GATHER_*_STATS procedures. The scope of any changes that were made was all subsequent operations. In Oracle Database 11g, the DBMS_STATS.SET_PARM procedure has been deprecated and it has been replaced with a set of procedures that allow you to set a preference for each parameter at a table, schema, database, and Global level. These new procedures are called DBMS_STATS.SET_*_PREFS and offer a much finer granularity of control.

However there has been some confusion around which procedure you should use when and what the hierarchy is among these procedures. In this post we hope to clear up the confusion. Lets start by looking at the list of parameters you can change using the DBMS_STAT.SET_*_PREFS procedures.

  • AUTOSTATS_TARGET (SET_GLOBAL_PREFS only)
  • CASCADE
  • DEGREE
  • ESTIMATE_PERCENT
  • METHOD_OPT
  • NO_INVALIDATE
  • GRANULARITY
  • PUBLISH
  • INCREMENTAL
  • STALE_PERCENT

As mentioned above there are four DBMS_STATS.SET_*_PREFS procedures.

  1. SET_TABLE_PREFS

  2. SET_SCHEMA_PREFS

  3. SET_DATABASE_PREFS

  4. SET_GLOBAL_PREFS


The DBMS_STATS.SET_TABLE_PREFS procedure allows you to change the default values of the parameters used by the DBMS_STATS.GATHER_*_STATS procedures for the specified table only.

The DBMS_STATS.SET_SCHEMA_PREFS procedure allows you to change the default values of the parameters used by the DBMS_STATS.GATHER_*_STATS procedures for all of the existing objects in the specified schema. This procedure actually calls DBMS_STATS.SET_TABLE_PREFS for each of the tables in the specified schema. Since it uses DBMS_STATS.SET_TABLE_PREFS calling this procedure will not affect any new objects created after it has been run. New objects will pick up the GLOBAL_PREF values for all parameters.

The DBMS_STATS.SET_DATABASE_PREFS procedure allows you to change the default values of the parameters used by the DBMS_STATS.GATHER_*_STATS procedures for all of the user defined schemas in the database. This procedure actually calls DBMS_STATS.SET_TABLE_PREFS for each of the tables in each of the user defined schemas. Since it uses DBMS_STATS.SET_TABLE_PREFS this procedure will not affect any new objects created after it has been run. New objects will pick up the GLOBAL_PREF values for all parameters. It is also possible to include the Oracle owned schemas (sys, system, etc) by setting the ADD_SYS parameter to TRUE.

The DBMS_STATS.SET_GLOBAL_PREFS procedure allows you to change the default values of the parameters used by the DBMS_STATS.GATHER_*_STATS procedures for any object in the database that does not have an existing table preference. All parameters default to the global setting unless there is a table preference set or the parameter is explicitly set in the DBMS_STATS.GATHER_*_STATS command. Changes made by this procedure will affect any new objects created after it has been run as new objects will pick up the GLOBAL_PREF values for all parameters.

With GLOBAL_PREFS it is also possible to set a default value for one additional parameter, called AUTOSTAT_TARGET. This additional parameter controls what objects the automatic statistic gathering job (that runs in the nightly maintenance window) will look after. The possible values for this parameter are ALL,ORACLE, and AUTO. ALL means the automatic statistics gathering job will gather statistics on all objects in the database. ORACLE means that the automatic statistics gathering job will only gather statistics for Oracle owned schemas (sys, sytem, etc) Finally AUTO (the default) means Oracle will decide what objects to gather statistics on. Currently AUTO and ALL behave the same.

In summary, DBMS_STATS obeys the following hierarchy for parameter values, parameters values set in the DBMS_STAT.GATHER*_STATS command over rules everything. If the parameter has not been set in the command we check for a table level preference. If there is no table preference set we use the global preference.

Thursday, July 23, 2009

Will the Optimizer Team be at Oracle Open World 2009?

With only two and a half months to go until Oracle Open World in San Francisco, October 11-15th, we have gotten several requests asking if we plan to present any session at the conference.

We have two session and a demo station in the Database campground at this year's show. We will give a technical presentation on What to Expect from the Oracle Optimizer When Upgrading to Oracle Database 11g and the Oracle Optimizer Roundtable.

The technical session, which is on Tuesday Oct 13 at 2:30 pm, gives step by step instructions and detailed examples of how to use the new 11g features to ensure your upgrade goes smoothly and without any SQL plan regressions.

The roundtable, which is on Thursday Oct. 15th at 10:30 am, will give you a first hand opportunity to pose you burning Optimizer and statistics questions directly to a panel of our leading Optimizer developers. In fact if you plan to attend the roundtable and already know what questions you would like to ask, then please send them to us via email and we will be sure to include them. Other wise, you can hand in your questions at our demo station at any stage during the week, or as you enter the actual session. Just be sure to write your questions in clear block capitals!

We look forward to seeing you all at Oracle Open World.

Tuesday, May 26, 2009

Why do I have hundreds of child cursors when cursor_sharing set to similar in 10g

Recently we received several questions regarding a usual situation where a SQL Statement has hundreds of child cursors. This is in fact the expected behavior when

  1. CURSOR_SHARING is set to similar

  2. Bind peeking is in use

  3. And a histogram is present on the column used in the where clause predicate of query

You must now be wondering why this is the expected behavior. In order to explain, let's step back and begin by explaining what CURSOR_SHARING actually does. CURSOR_SHARING was introduced to help relieve pressure put on the shared pool, specifically the cursor cache, from applications that use literal values rather than bind variables in their SQL statements. It achieves this by replacing the literal values with system generated bind variables thus reducing the number of (parent) cursors in the cursor cache. However, there is also a caveat or additional requirement on CURSOR_SHARING, which is that the use of system generated bind should not negatively affect the performance of the application. CURSOR_SHARING has three possible values: EXACT, SIMILAR, and FORCE. The table below explains the impact of each setting with regards to the space used in the cursor cache and the query performance.









CURSOR_SHARING VALUESPACE USED IN SHARED POOLQUERY PERFORMANCE
EXACT (No literal replacement)Worst possible case - each stmt issued has its own parent cursorBest possible case as each stmt has its own plan generated for it based on the value of the literal value present in the stmt
FORCEBest possible case as only one parent and child cursor for each distinct stmtPotentially the worst case as only one plan will be used for each distinct stmt and all occurrences of that stmt will use that plan
SIMILAR without histogram presentBest possible case as only one parent and child cursor for each distinct stmtPotentially the worst case as only one plan will be used for each distinct stmt and all occurrences of that stmt will use that plan
SIMILAR with histogram presentNot quite as much space used as with EXACT but close. Instead of each stmt having its own parent cursor they will have their own child cursor (which uses less space)Best possible case as each stmt has its own plan generated for it based on the value of the literal value present in the stmt



In this case the statement with hundreds of children falls into the last category in the above table, having CURSOR_SHARING set to SIMILAR and a histogram on the columns used in the where clause predicate of the statement. The presence of the histogram tells the optimizer that there is a data skew in that column. The data skew means that there could potentially be multiple execution plans for this statement depending on the literal value used. In order to ensure we don't impact the performance of the application, we will peek at the bind variable values and create a new child cursor for each distinct value. Thus ensuring each bind variable value will get the most optimal execution plan. It's probably easier to understand this issue by looking at an example. Let's assume there is an employees table with a histogram on the job column and CURSOR_SHARING has been set to similar. The following query is issued

select * from employees where job = 'Clerk';

The literal value 'Clerk' will be replaced by a system generated bind variable B1 and a parent cursor will be created as

select * from employees where job = :B1;

The optimizer will peek the bind variable B1 and use the literal value 'Clerk' to determine the execution plan. 'Clerk' is a popular value in the job column and so a full table scan plan is selected and child cursor C1 is created for this plan. The next time the query is executed the where clause predicate is job='VP' so B1 will be set to 'VP', this is not a very popular value in the job column so an index range scan is selected and child cursor C2 is created. The third time the query is executed the where clause predicate is job ='Engineer' so the value for B1 is set to 'Engineer'. Again this is a popular value in the job column and so a full table scan plan is selected and a new child cursor C3 is created. And so on until we have seen all of the distinct values for job column. If B1 is set to a previously seen value, say 'Clerk', then we would reuse child cursor C1.







Value for B1Plan UsedCursor Number
ClerkFull Table ScanC1
VPIndex Range ScanC2
EngineerFull Table ScanC3



As each of these cursors is actually a child cursor and not a new parent cursor you will still be better off than with CURSOR_SHARING set to EXACT as a child cursor takes up less space in the cursor cache. A child cursor doesn't contain all of the information stored in a parent cursor, for example, the SQL text is only stored in the parent cursor and not in each child.

Now that you know the explanation for all of the child cursors you are seeing you need to decide if it is a problem for you and if so which aspect affects you most, space used in the SHARED_POOL or query performance. If your goal is to guarantee the application performance is not affected by setting CURSOR_SHARING to SIMILAR then keep the system settings unchanged. If your goal is to reduce the space used in the shared pool then you can use one of the following solutions with different scopes:

  1. Individual SQL statements - drop the histograms on the columns for each of the affected SQL statements

  2. System-wide - set CURSOR_SHARING to FORCE this will ensure only one child cursor per SQL statement


Both of these solutions require testing to ensure you get the desired effect on your system. Oracle Database 11g provides a much better solution using the Adaptive Cursor Sharing feature. In Oracle Database 11g, all you need to do is set CURSOR_SHARING to FORCE and keep the histograms. With Adaptive Cursor Sharing, the optimizer will create a cursor only when its plan is different from any of the plans used by other child cursors. So in the above example, you will get two child cursors (C1 and C2) instead of 3.

Sunday, April 12, 2009

Update on Adaptive Cursor Sharing

In December 2007 we did a post on the new Oracle Database 11g feature Adaptive Cursor Sharing called Why are there more cursor in 11g for my queries containing binds. In this post we explained that in Oracle Database 11g it was possible to have multiple execution plans for a single statement that uses bind variables, thus ensuring that the best execution plan will be used depending on the bind value. However, it's been brought to our attention by Ric Van Dyke from Hotsos that you may get a lot more cursors than you were expecting especially if you are using 11.1.0.6. So why is that?

In Ric's case he had a simple query


Select /* ACS_1 */ count(c1) from t where c1 = :B1;



The column c1 has 12 distinct values but the data in that column is skewed. There are three possible execution plans for this query,

Full table scan
Index range scan
Fast full scan

When Ric ran through all 12 distinct values for B1 he found he got 11 child cursors in 11.1.0.6, while with 11.1.0.7 the same test only generates 7 child cursors. Two code changes went into 11.1.0.7 to reduce the number of child cursors, which is why there are only 7 but why do we see 7 when there are only 3 possible plans?

As we discussed in our original blog post the first time this statement is executed the optimizer peeked at the actual value of the bind variable b1 and uses that value to determine the initial execution plan. The next value passed in for B1 will use the same plan but behind the scenes, Oracle monitors the behavior of the queries, and determined that the different bind values caused the data volumes manipulated by the query to be significantly different. Based on this difference, Oracle "adapts" its behavior so that the same plan is not always shared for this query. Hence on the next execution, a new plan is generated based on the current bind value. So now the child cursor count is 2.


SQL> select child_number, executions, buffer_gets,
2 is_bind_sensitive, is_bind_aware
3 from v$sql
4 where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 2 600 Y N
1 1 1821 Y Y


The new child cursor is marked bind aware. As Ric ran through all 12 distinct values (running each value twice) Oracle continued to consider whether the current bind value was expected to result in a significantly different data volume than those bind values for which cursors had already been created. Each time Oracle saw a significant change, it triggered the statement to be hard parsed. In Ric's case this happened 5 more times. During those additional 5 hard parses the optimizer came up with two distinct plans. But because Oracle did 5 hard parses, 5 new child cursors were generated even though some of those cursor had the same execution plan.


SQL> select child_number, executions, buffer_gets,
2 is_bind_sensitive, is_bind_aware
3 from v$sql
4 where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 2 600 Y N
1 1 1821 Y Y
2 1 107 Y Y
3 1 218 Y Y
4 1 430 Y Y
5 1 821 Y Y
6 1 1007 Y Y

This is the expected behavior. However, if we look at how many of these cursors are marked shareable we find only two of the 7 cursors are , which means the other 5 cursors will be aged out if cache space is required. So, they should not cause any contention.


SQL> select child_number, executions,
2 is_shareable
3 from v$sql
4 where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS IS_SHAREABLE
------------ ---------- -------------
0 2 N
1 1 N
2 1 N
3 1 N
4 1 N
5 1 Y
6 1 Y


So, as the system get busier and the non-shareable plans age out we will eventually have at most 3 cursors for this SQL statement (one for each execution plan), as you would expect.

You might wonder if this behavior scales with the number of bind values. In other words, if you run the query with 20 different bind values, would that result in 10 or more hard parses? This is not the case. During the initial hard parses, the optimizer is essentially mapping out the relationship between bind values and the appropriate execution plan. After this initial ramp up period, a steady state will eventually be reached, so that executing with a new bind value will result in picking the best child cursor in the cache, without requiring a hard parse. In fact, Ric's test table contained over 30 possible values for the bind (though he only used 12 in his test queries). Changing the test to use all of the possible values results in only one additional hard parse.