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

Tuesday, November 17, 2009

What should I do with old hints in my workload?

We promised in an earlier post to cover some of the questions from the Optimizer round table discussion at Oracle Open World. Here's our first in a series of posts that will address these questions.

Q: When moving from 10g to 11g, should hints in existing SQL be removed?

A: I was glad to see this question at the round table, since I think this is something people often overlook when adding hints to an application. Over time, hints can become stale. You may have added a hint to a query because of a weakness in an old version of the optimizer, but the hint might no longer be necessary to get the best plan. Even worse, the hint may force a plan which is suboptimal, for a couple of reasons:
  • Depending on how your data change over time, the plan choice that the hint enforces might no longer be a good choice. This problem can occur even without a database upgrade. For example, if the distribution of values in a column change over time, an old access path hint may no longer be appropriate.
  • The hint might prevent new optimizations from taking place. So after an upgrade, you will be stuck using the old hint-enforced plan, when the optimizer could have chosen something better. For example, you may have hinted a plan for a query with bind variables, to avoid a bad plan choice due to bind peeking in the pre-11g optimizer. In 11g, you will not get the benefits of adaptive cursor sharing because of the hints in the query.
These are just some of the reasons why it is good to re-evaluate the hints in your application from time to time. You can test this out using the _optimizer_ignore_hints parameter. Setting this parameter to TRUE will cause the optimizer to ignore the hints embedded in queries. You can set this on the session level, run your workload, and compare the performance to your baseline performance (with the hints). I recommend using SQL Performance Analyzer (SPA) to do this. Read the SPA white paper for more information on how to do that.

If you find that some of your queries really do still require hints to get an optimal plan, you should consider creating a SQL plan baseline, and allowing SPM to manage the plan for you. If you do this, then you will get the hinted plan for now, but if a better plan comes along later (for one of the reasons mentioned earlier), you will eventually get the benefits of the new plan, using the SPM evolve process. There are two ways that you can create a SQL plan baseline based on your hints that will be applied to the query without hints. The first way requires that a SQL plan baseline already exist for the unhinted query. That's described in an earlier post. Here are the steps to use if you don't already have a SQL plan baseline for the unhinted query.

1. Run the query with hints, and confirm that the plan is what you want:

var pid number
exec :pid := 100;

select /*+ leading(t) */ p.prod_name, s.amount_sold, t.calendar_year
from sales s, products p, times t
where s.prod_id = p.prod_id
and s.time_id = t.time_id
and p.prod_id < :pid;
PROD_NAME                                          AMOUNT_SOLD CALENDAR_YEAR
-------------------------------------------------- ----------- -------------
...

select sql_id from v$sql where sql_text like 'select /*+ leading(t) */ p.prod_name%';
SQL_ID
-------------
2qtu6hy4rf1j9

select * from table(dbms_xplan.display_cursor(sql_id=>'2qtu6hy4rf1j9',
format=>'basic note'));

EXPLAINED SQL STATEMENT:
------------------------
select /*+ leading(t) */ p.prod_name, s.amount_sold, t.calendar_year
from sales s, products p, times t where s.prod_id = p.prod_id and
s.time_id = t.time_id and p.prod_id < :pid

Plan hash value: 2290436051
---------------------------------------------------------------
| Id | Operation | Name |
---------------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | HASH JOIN | |
| 2 | HASH JOIN | |
| 3 | TABLE ACCESS FULL | TIMES |
| 4 | PARTITION RANGE ALL | |
| 5 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES |
| 6 | BITMAP CONVERSION TO ROWIDS | |
| 7 | BITMAP INDEX RANGE SCAN | SALES_PROD_BIX |
| 8 | TABLE ACCESS BY INDEX ROWID | PRODUCTS |
| 9 | INDEX RANGE SCAN | PRODUCTS_PK |
---------------------------------------------------------------


2. Load SQL plan baseline from cursor cache, and associate it with the unhinted query:

var sqltext clob;

begin
:sqltext := 'select p.prod_name, s.amount_sold, t.calendar_year
from sales s, products p, times t
where s.prod_id = p.prod_id
and s.time_id = t.time_id
and p.prod_id > :pid';
end;
/

exec :pls := dbms_spm.load_plans_from_cursor_cache( -
sql_id => '2qtu6hy4rf1j9', -
plan_hash_value => 2290436051, -
sql_text => :sqltext);


3. Run the query without hints, and check that the SQL plan baseline was used.

select p.prod_name, s.amount_sold, t.calendar_year
from sales s, products p, times t
where s.prod_id = p.prod_id
and s.time_id = t.time_id
and p.prod_id < :pid;


PROD_NAME AMOUNT_SOLD CALENDAR_YEAR

-------------------------------------------------- ----------- -------------
...


select * from table(dbms_xplan.display_cursor(sql_id=>'a1ax3265pq8x7',
format=>'basic note'));


EXPLAINED SQL STATEMENT:
------------------------
select p.prod_name, s.amount_sold, t.calendar_year from sales s,
products p, times t where s.prod_id = p.prod_id and s.time_id =
t.time_id and p.prod_id < :pid

Plan hash value: 2290436051



---------------------------------------------------------------
| Id | Operation | Name |
---------------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | HASH JOIN | |
| 2 | HASH JOIN | |
| 3 | TABLE ACCESS FULL | TIMES |
| 4 | PARTITION RANGE ALL | |
| 5 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES |
| 6 | BITMAP CONVERSION TO ROWIDS | |
| 7 | BITMAP INDEX RANGE SCAN | SALES_PROD_BIX |
| 8 | TABLE ACCESS BY INDEX ROWID | PRODUCTS |
| 9 | INDEX RANGE SCAN | PRODUCTS_PK |
---------------------------------------------------------------
Note
-----
- SQL plan baseline SQL_PLAN_4rw2dhryc2w5h888547d3 used for this statement

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.