Insights into Statistics, Query Optimization and the Oracle Optimizer

Using SQL Plan Management to Control SQL Execution Plans

Nigel Bayliss
Product Manager

SQL plan management (SPM) is designed to prevent performance regression for all SQL statements used by an application (assuming that the SQL statements in question are used more than once). SPM uses SQL plan baselines that are associated with individual SQL statements to control what execution plans they are permitted to use. It’s a simple but powerful idea that opens the door to the possibility of using SQL plan baselines in a more selective and reactive way: to influence the SQL execution plans of individual queries without having to modify application queries or change the application itself. The technique is covered in blogs and in the SPM white paper here, but it deserves a post of its own along with a full example. 

If you want to avoid reading all the background below and jump to a real example, I've added some new scripts to GitHub. They are similar to SQL profile examples published by Oracle Support some years ago and they demonstrate how you can retrieve plans from SQL tuning sets and AWR. They will work in Oracle Database 12c Release 2 onwards (and even on Oracle Database 18c Standard Edition). The other code example below will work in Oracle Database 11g onwards.

Consider the scenario where you have a SQL statement used by an application that’s got a sub-optimal plan and you need to do something about it. For the sake of argument, let’s assume that you know that there’s a hint you can use to achieve a better plan. I’m going to assume from now on that you want to apply a hint but the application code cannot be changed in any way.

Take a look at the following SQL execution plan. It’s an application query that filters SALES rows using an index:

  2  FROM table(DBMS_XPLAN.DISPLAY_CURSOR(sql_id=>'f23qunrkxdgdt'));
SQL_ID  f23qunrkxdgdt, child number 2
select sum(num) from sales where id < :idv

Plan hash value: 2327341677
| Id  | Operation                            | Name   |
|   0 | SELECT STATEMENT                     |        |
|   1 |  SORT AGGREGATE                      |        |
|*  3 |    INDEX RANGE SCAN                  | SALESI |

What if this plan isn’t optimal? It is optimal in this case, but for the sake of example I’m going to assume that I want the Oracle Optimizer to pick a full table scan instead.  All we need is the FULL hint:

SQL_ID  82x4tj3z2vg23, child number 0
select /*+ FULL(sales) */ sum(num) from sales where id < :idv
Plan hash value: 1047182207
| Id  | Operation          | Name  |
|   0 | SELECT STATEMENT   |       |
|   1 |  SORT AGGREGATE    |       |

The hinted test query gives us an example of the TABLE ACCESS FULL plan we want to use. At this point we are in a position to use SPM to associate our preferred plan with the application query.  Here are the steps:

Controlling plans with SQL plan management

Step zero acknowledges that we have a SQL statement with a plan we want to change. The remaining steps are:

  1. Create an initial disabled SQL plan baselines for the application query. I’m using the term “one or more” because a query might have more than one SQL execution plan.  We will create a SQL plan baseline for each plan used by the SQL statement, but only one is actually needed.
  2. Execute (or parse) a hinted test query to generate the preferred plan.
  3. Load the preferred plan into a SQL plan baseline created in step two (this time with enabled=’YES’).

The hinted statement’s text is of course different to the application statement’s text, but that’s just fine: we’re simply using the plan and not the SQL text. Our application query will use the plan as long as it can reproduce it and it’s valid. What do I mean by that? Here’s an example:

Imagine a CUSTOMERS query that happens to perform a FULL scan:

select sum(num) from CUSTOMERS;

If we use the plan for this query in an attempt to influence our SALES query, it’s not going to work.  We would be asking the SQL plan baseline to influence the SALES plan like this:

select /*+ FULL(customers) */ sum(num) from SALES where id < :idv

Under the covers, SQL plan baselines use a complete set of hints to control execution plans. So, for our SALES query, FULL(customers) is not a valid hint and is not going to yield the desired result! If you’ve got some time on your hands, you can try loading a plan for a CUSTOMERS query into a SQL plan baseline associated with a SALES query. There won’t be an error message, but you won’t be able to reproduce the plan you want either (unless it’s just by luck).

Worked Example

I’ve have uploaded an example procedure and a fully worked example to GitHub so you can see how the steps above can be implemented. Based on a comment below, I added this procedure too. It loads all existing plans in a disabled state and adds a new enabled SQL plan baseline (rather than replacing an existing one). You should adapt the procedures to meet your specific requirements. For example, you might not want to drop pre-existing SQL plan baselines.

I’ll be using the SQL IDs and plan hash value that I highlighted in bold, above. Here’s how to use my example procedures set_my_plan and add_my_plan (see proc.sql and proc2.sql in GitHub):

Set my plan procedure

Executing the Procedures

Note that “SPB” stands for SQL plan baseline:

SQL> set serveroutput on
SQL> set linesize 200
SQL> exec set_my_plan('f23qunrkxdgdt','82x4tj3z2vg23',1047182207)
No existing SQL plan baselines to drop
Created 1 disabled SPBs for SQLID f23qunrkxdgdt
SPB Detail: SQL_PLAN_3yr9p97b3j5gbfaa7aab3 handle SQL_3f5d3549d63895eb
Associating plan SQLID/PHV 82x4tj3z2vg23/1047182207 with SPB SQL Handle SQL_3f5d3549d63895eb
Enabled SPB - Name: SQL_PLAN_3yr9p97b3j5gb35032dee SQL handle: SQL_3f5d3549d63895eb
SQL> set serveroutput off

Here’s the explain plan for the application query after the procedure was executed. The non-hinted SQL statement now uses the FULL scan and you can see from the Note section that the SQL plan baseline is being used.

SQL_ID  f23qunrkxdgdt, child number 0
select sum(num) from sales where id < :idv

Plan hash value: 1047182207
| Id  | Operation          | Name  |
|   0 | SELECT STATEMENT   |       |
|   1 |  SORT AGGREGATE    |       |
Predicate Information (identified by operation id):
   2 - filter("ID"<:IDV)

   - SQL plan baseline SQL_PLAN_3yr9p97b3j5gb35032dee used for this statement

Usage Notes

SPM matches a SQL statement using a signature, not a SQL ID. The signature is generated from the normalized SQL text. For this reason, if there are multiple SQL IDs that have the same signature then they will all share the same SQL plan baseline. For example, the following queries have the same signature:

select sum(num) from sales where id < :idv
SELECT SUM(num) FROM sales WHERE id < :idv
select      sum(num) from sales where id < :idv

The example procedures (above) will drop any pre-existing SQL plan baselines for SQL statements that have the same signature as the application SQL statement. The newer scripts will generate an error if there are existing SQL plan baselines unless you use the FORCE parameter.

As always, comments and corrections are welcome. Just post a comment at the bottom.

Join the discussion

Comments ( 5 )
  • Houri Mohamed Tuesday, September 12, 2017
    Hi Nigel

    Thanks for sharing this new way to transfer a SPM baseline between two sql_id. Do you see any drawback in the method I am using to do the same thing but much simpler than what you've proposed?


    The script TransferSPM is given at the end of the above article

    Best regards
    Mohamed Houri
  • Nigel Bayliss Wednesday, September 13, 2017
    Hi Mohamed,

    Sure, I don't see any obvious problem. Interesting - I like the idea of adding a NEW SQL plan baseline because you can keep all existing plans in a DISABLED state and then add the new one ENABLED. This means existing plans are effectively disabled and not subject to evolution - something you might want. Any new plans found by the optimizer will be captured in the SQL plan history and subject to evolution. Pretty nice - so I took your example and added the pieces I like to create a "proc2.sql" script. See above.

  • Kaushal Roonwal Friday, April 12, 2019
    Hi, is there a way to give the fresh stats on a table to an already running query?
  • Nigel Bayliss Tuesday, April 23, 2019
    Hi - there's isn't a way to do this. Stats are used at parse-time, so once the query is running its plan is (mostly) fixed anyway. There are adaptive plans in Oracle Database 12c, but plans are modified in response to runtime information rather than statistics.
  • angelo mandelli Thursday, April 23, 2020
    La prima non funziona, dice "wrong number or types of arguments in call to 'LOAD_PLANS_FROM_CURSOR_CACHE'"
    Ma perchè non scrivi le cose giuste!
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.