Wouldn’t “Automatic SQL Plan Management” be the 11g “killer app” ?

Posted in: Technical Track

I’m just wondering… be careful it is part of Oracle Tuning Pack 11g !

Anyway, before you answer this question, lets illustrate with an example how the “non-automatic”, “no tuning Pack” SQL Plan Management works. This 11g new feature completes (replaces ?) the outlines by providing a new plan stability capability to Oracle database 11g Enterprise Edition.

If you want the truth behind this example, you should refer before anything else to the 11g documentation :

Step 1 : Sample schema

We’ll need a table with a couple of rows to demonstrate this feature :

create table gark
    (id number not null);
begin
  for i in 1..10000 loop
    insert into gark(id)
               values (i);
  end loop;
  commit;
end;
/

exec dbms_stats.gather_table_stats(user, 'GARK')

Step 2 : Query and Plan

We’ll need to use SQL*Plus ! I haven’t been able to use SQL*Developer as the last session query, from my experience is not the one you’ve just run even with “set serveroutput off” and I don’t want to use “explain plan” neither “set autotrace on”. Connected as the sample user (Let’s say SCOTT), run :

set serveroutput off
select a.id,
       b.id
  from gark a,
       gark b
 where a.id=500
   and b.id=a.id;

select * from table(dbms_xplan.display_cursor);

The query plan should look like this :

Execution Plan
----------------------------------------------------------
Plan hash value: 2625395012
----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     4 |    24 |    23   (5)|
|*  1 |  HASH JOIN         |      |     4 |    24 |    23   (5)|
|*  2 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
|*  3 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
----------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("B"."ID"="A"."ID")
2 - filter("A"."ID"=500)
3 - filter("B"."ID"=500)

Step 3 : Register the plan in the SQL Plan Baselines

To perform this operation, you have to know the query SQL_ID in addition to the PLAN_HASH_VALUE that is display in the plan output :

accept plan_hash_value prompt "Enter value for plan_hash_value: "
2625395012

select distinct sql_id, plan_hash_value
    from v$sql_plan
    where plan_hash_value=&plan_hash_value;

SQL_ID        PLAN_HASH_VALUE
------------- ---------------
4pznd20f4x8tf      2625395012

The query below display the registered SQL Plan baselines. It should be empty unless we’ve set the optimizer_capture_sql_plan_baselines parameter to true :

col SQL_HANDLE format a24
col PLAN_NAME format a29
col SCHEMA format a8

select SQL_HANDLE,
       PLAN_NAME,
       PARSING_SCHEMA_NAME SCHEMA,
       ENABLED,
       ACCEPTED,
       SQL_TEXT
  from dba_sql_plan_baselines;

no row selected.

You can now register the current query plan with the script below :

accept sql_id prompt "Enter value for sql_id: "
4pznd20f4x8tf

declare
  gg binary_integer;
begin
  gg:=dbms_spm.load_plans_from_cursor_cache(
                 sql_id=>'&sql_id');
end;
/

PL/SQL procedure successfully completed.

select SQL_HANDLE,
       PLAN_NAME,
       PARSING_SCHEMA_NAME SCHEMA,
       ENABLED,
       ACCEPTED,
       SQL_TEXT
  from dba_sql_plan_baselines;
SQL_HANDLE               PLAN_NAME                     SCHEM ENA ACC
------------------------ ----------------------------- ----- --- ---
SQL_TEXT
--------------------------------------------------------------------
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b SCOTT YES YES
select a.id,
  b.id
from gark a,
  gark b
where a.id=500
and b.id=a.id

Step 4 : the registered SQL Plan Baseline prevents an other plan to be used

To check the behavior of of the SQL Plan Management feature, we’ll perform what follow :

  • Create an index the query could benefit from
  • Check the content of the SQL Plan Baselines after the index creation
  • Flush the Shared Pool and check the plan will be parsed
  • Run the query and check its plan

1- Create an index

create index gark_idx
       on gark(id);

Index created.

2- Look at the SQL Plan baselines with the DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE table function ; The registered plan does not use the index :

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa

select * from table(
   dbms_xplan.display_sql_plan_baseline(
          sql_handle=>'&sql_handle'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------
SQL handle: SYS_SQL_9ff3489361e958aa
SQL text: select a.id, b.id from gark a, gark b where a.id=500
          and b.id=a.id
----------------------------------------------------------------
----------------------------------------------------------------
Plan name: SYS_SQL_PLAN_61e958aa45bb399b
Enabled: YES Fixed: NO Accepted: YES Origin: MANUAL-LOAD
----------------------------------------------------------------
Plan hash value: 2625395012
----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     4 |    24 |    23   (5)|
|*  1 |  HASH JOIN         |      |     4 |    24 |    23   (5)|
|*  2 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
|*  3 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
----------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("B"."ID"="A"."ID")
   2 - filter("A"."ID"=500)
   3 - filter("B"."ID"=500)
ERROR: User has no SELECT privileges on objects of the SQL
       plan baseline

3- Flush the shared Pool :

alter system flush shared_pool;

System altered.

accept sql_id prompt "Enter value for sql_id: "
4pznd20f4x8tf

select sql_id,
       plan_hash_value
  from v$sql
 where sql_id='&sql_id';

no rows selected

4- Run the query and figured out that the query plan does not change besides the new index and the fact it has been re-parsed :

set serveroutput off

select a.id,
       b.id
  from gark a,
       gark b
 where a.id=500
   and b.id=a.id;

select * from table(dbms_xplan.display_cursor);

Execution Plan
----------------------------------------------------------
Plan hash value: 2625395012
----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     4 |    24 |    23   (5)|
|*  1 |  HASH JOIN         |      |     4 |    24 |    23   (5)|
|*  2 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
|*  3 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
----------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("B"."ID"="A"."ID")
   2 - filter("A"."ID"=500)
   3 - filter("B"."ID"=500)

Note
-----
- SQL plan baseline SYS_SQL_PLAN_61e958aa45bb399b used for this statement

If you display the SQL plan baselines, you’ll see a new plan for the query has been registered which attributes are ACCEPTED=’NO’ and ORIGIN=’AUTO-CAPTURE’. This plan is not used by the query :

select SQL_HANDLE,
       PLAN_NAME,
       PARSING_SCHEMA_NAME SCHEMA,
       ENABLED,
       ACCEPTED,
       SQL_TEXT
  from dba_sql_plan_baselines;
SQL_HANDLE               PLAN_NAME                     SCHEM ENA ACC
------------------------ ----------------------------- ----- --- ---
SQL_TEXT
--------------------------------------------------------------------
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b SCOTT YES YES
select a.id,
  b.id
from gark a,
  gark b
where a.id=500
and b.id=a.id
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa SCOTT YES NO
select a.id,
  b.id
from gark a,
  gark b
where a.id=500
and b.id=a.id

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa

select * from table(
   dbms_xplan.display_sql_plan_baseline(
          sql_handle=>'&sql_handle'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------
SQL handle: SYS_SQL_9ff3489361e958aa
SQL text: select a.id, b.id from gark a, gark b where a.id=500
          and b.id=a.id
----------------------------------------------------------------
----------------------------------------------------------------
Plan name: SYS_SQL_PLAN_61e958aa45bb399b
Enabled: YES Fixed: NO Accepted: YES Origin: MANUAL-LOAD
----------------------------------------------------------------
Plan hash value: 2625395012
----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     4 |    24 |    23   (5)|
|*  1 |  HASH JOIN         |      |     4 |    24 |    23   (5)|
|*  2 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
|*  3 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
----------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("B"."ID"="A"."ID")
   2 - filter("A"."ID"=500)
   3 - filter("B"."ID"=500)

----------------------------------------------------------------
Plan name: SYS_SQL_PLAN_61e958aafcd784aa
Enabled: YES Fixed: NO Accepted: NO Origin: AUTO-CAPTURE
----------------------------------------------------------------
Plan hash value: 886284999
-------------------------------------------------------------------
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%)|
-------------------------------------------------------------------
|   0 | SELECT STATEMENT     |          |     1 |     6 |   3 (34)|
|   1 |  MERGE JOIN CARTESIAN|          |     1 |     6 |   3 (34)|
|*  2 |   INDEX RANGE SCAN   | GARK_IDX |     1 |     3 |   1  (0)|
|   3 |   BUFFER SORT        |          |     1 |     3 |   2 (50)|
|*  4 |    INDEX RANGE SCAN  | GARK_IDX |     1 |     3 |   1  (0)|
-------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("A"."ID"=500)
   4 - access("B"."ID"=500)

Step 5 : Evolve the plan
The evolve_sql_plan_baseline enable to accept the new SQL plan :

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa

var v_out clob
exec :v_out:=dbms_spm.evolve_sql_plan_baseline(-
   sql_handle=>'&sql_handle')
print v_out

select SQL_HANDLE,
       PLAN_NAME,
       ENABLED,
       ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE               PLAN_NAME                     ENA ACC
------------------------ ----------------------------- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b YES YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa YES YES

Once accepted, if you execute the query, the new plan (with the index) will be used :

select a.id,
       b.id
from gark a,
     gark b
where a.id=500
and b.id=a.id;

select * from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
----------------------
SQL_ID  4pznd20f4x8tf, child number 0
-------------------------------------
select a.id, b.id from gark a, gark b where a.id=500  and
b.id=a.id
Plan hash value: 886284999
-------------------------------------------------------------------
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%)|
-------------------------------------------------------------------
|   0 | SELECT STATEMENT     |          |     1 |     6 |   3 (34)|
|   1 |  MERGE JOIN CARTESIAN|          |     1 |     6 |   3 (34)|
|*  2 |   INDEX RANGE SCAN   | GARK_IDX |     1 |     3 |   1  (0)|
|   3 |   BUFFER SORT        |          |     1 |     3 |   2 (50)|
|*  4 |    INDEX RANGE SCAN  | GARK_IDX |     1 |     3 |   1  (0)|
-------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("A"."ID"=500)
   4 - access("B"."ID"=500)
Note
-----
- SQL plan baseline SYS_SQL_PLAN_61e958aafcd784aa used for this
  statement

Step 6 : One Step Back

What if I just accept a new plan and it is indeed not efficient ? You can of cause drop the plan from the SQL Plan baselines as described in Step 7 or you can use the FIXED attribute which set the plans that have to be used. Using this attribute enables you to easily go back and forward between a set of plans :

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa
accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aa45bb399b

declare
  v_out binary_integer;
begin
   v_out := dbms_spm.ALTER_SQL_PLAN_BASELINE(
           sql_handle =>'&sql_handle',
           plan_name=>'&plan_name',
           attribute_name=>'fixed',
           attribute_value=>'YES');
end;
/

select SQL_HANDLE,
       PLAN_NAME,
       FIXED,
       ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE               PLAN_NAME                     FIX ACC
------------------------ ----------------------------- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b YES YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa NO  YES

select a.id,
       b.id
  from gark a,
       gark b
 where a.id=500
   and b.id=a.id;

select * from table(dbms_xplan.display_cursor);

Execution Plan
----------------------------------------------------------
Plan hash value: 2625395012
----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     4 |    24 |    23   (5)|
|*  1 |  HASH JOIN         |      |     4 |    24 |    23   (5)|
|*  2 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
|*  3 |   TABLE ACCESS FULL| GARK |     2 |     6 |    11   (0)|
----------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("B"."ID"="A"."ID")
   2 - filter("A"."ID"=500)
   3 - filter("B"."ID"=500)

Note
-----
- SQL plan baseline SYS_SQL_PLAN_61e958aa45bb399b used for this statement

To re set the fixed attribute run what’s below :

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa
accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aa45bb399b

declare
  v_out binary_integer;
begin
   v_out := dbms_spm.ALTER_SQL_PLAN_BASELINE(
           sql_handle =>'&sql_handle',
           plan_name=>'&plan_name',
           attribute_name=>'fixed',
           attribute_value=>'NO');
end;
/

select SQL_HANDLE,
       PLAN_NAME,
       FIXED,
       ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE               PLAN_NAME                     FIX ACC
------------------------ ----------------------------- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b NO  YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa NO  YES

Step 7. Drop the SQL Plan baselines

To drop the registered SQL Plan baselines, you can use the drop_sql_plan_baseline function from dbms_spm as below :

col schema format a5
col SQL_HANDLE format a24
select SQL_HANDLE,
  PLAN_NAME,
  PARSING_SCHEMA_NAME SCHEMA,
  ENABLED,
  ACCEPTED
from dba_sql_plan_baselines;

SQL_HANDLE               PLAN_NAME                     SCHEM ENA ACC
------------------------ ----------------------------- ----- --- ---
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aa45bb399b SCOTT YES YES
SYS_SQL_9ff3489361e958aa SYS_SQL_PLAN_61e958aafcd784aa SCOTT YES YES

accept sql_handle prompt "Enter value for sql_handle: "
SYS_SQL_9ff3489361e958aa
accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aa45bb399b

DECLARE
  gg binary_integer;
BEGIN
  gg:=DBMS_SPM.DROP_SQL_PLAN_BASELINE(
          SQL_HANDLE => '&sql_handle',
          PLAN_NAME => '&plan_name');
END;
/

accept plan_name prompt "Enter value for plan_name: "
SYS_SQL_PLAN_61e958aafcd784aa

DECLARE
  gg binary_integer;
BEGIN
  gg:=DBMS_SPM.DROP_SQL_PLAN_BASELINE(
          SQL_HANDLE => '&sql_handle',
          PLAN_NAME => '&plan_name');
END;
/

col schema format a5
col SQL_HANDLE format a24
select SQL_HANDLE,
  PLAN_NAME,
  PARSING_SCHEMA_NAME SCHEMA,
  ENABLED,
  ACCEPTED
from dba_sql_plan_baselines;

no rows selected

Step 8. Introducing Automatic SQL Plan Management

In order to use Automatic SQL Plan Management, you’ll have to licence Oracle Tuning Pack and set optimizer_capture_sql_plan_baselines to true. Once done :

  • The instances capture the “repeatable” SQL and their plan during the “normal” period.
  • The new automatic maintenance schedule runs the SQL Tuning Advisor and set the SQL plans. As with 10g the SQL Tuning Advisor does not only consider the statistics but goes much deeper into SQL and plan analysis to evolve the plan…
  • While new plans can be consider, the SQL Tuning Advisor evolves plans during the Maintenance Window making plans probably more accurate and probably less candidate to sudden changes.

It looks a fantastic 11g feature, don’t you think ? The question now is what performance enhancement I should expect from it and a good way to evaluate it would be to run the command below on a production system… Who dares ?

alter system set optimizer_capture_sql_plan_baselines=true

Step 9 : Drop the sample schema

drop index gark_idx;
drop table gark;

Step 10 : To conclude

No doubt that in manual mode SQL Plan Management highly improves the way we manage plan stability. It enables to easily switch from one plan to the other, it can be exported/imported from one database to the other and as a good news **considering the best 11g new features seem to be part of options**, it’s part of the classic “EE”. Now I miss a real production use case of Automatic SQL Plan Management.

email
Want to talk with an expert? Schedule a call with our team to get the conversation started.

5 Comments. Leave new

where is it documented that optimizer_capture_sql_plan_baselines is part of the Tuning Pack?
followed the link at top “optimizer_capture_sql_plan_baselines (Tuning Pack)” but didn’t see any reference to the Tuning Pack.
Did a google search but didn’t see anything in the first 10 hits

Reply

here’s a link:
https://68.142.116.68/docs/cd/B28359_01/license.111/b28287/options.htm
says
“Automatic Plan Evolution of SQL Plan Management”
is part of the Tuning pack. Still unclear as to which commands are part of the pack and which or not.

Reply

Hello,

It looks like, I was wrong when I wrote that… Even if Oracle is not very clear about the SPM feature that is actually included in the Tuning Pack.

If you refer to the licensing guide, regarding Tuning Pack), it says:

Oracle Tuning Pack includes the following features:
[…]
* Automatic Plan Evolution of SQL Plan Management

The question is What is “Automatic Plan Evolution of SQL Plan Management” ? Obviously if you search the documentation for this feature, you find it only in the Licensing Guide.

I’ve disabled the Tuning pack and change the value and it doesn’t fail. The licensing terms suggest that’s actually included in that case! or, maybe, Oracle disables the feature, in spite of the parameter and doesn’t tell:
SQL> alter system set control_management_pack_access=DIAGNOSTIC;

System altered.

SQL> alter system set OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=true;

System altered.

So it’s worth more testing (at least to me) to figure out what exact part of SPM is included in the Tuning Pack Only!

Reply

SQL Plan Management is not part of the Tuning Pack. It is a free tool available to all users of Oracle Enterprise Edition. What Kyle found in the Oracle documentation is a documentation bug. The wording implies you need the Tuning Pack when you don’t. SQL Plan Management works in conjunction with the Tuning Pack specifically the auto SQL Tune feature but is not part of it.
Automatic Plan Evolution of SQL Plan Management means if the Auto SQL Tune advisor finds a better plan for a SQL statement that has a SQL Plan Baseline that plan will be automatically added to the plan baseline as an accepted plan. Nothing more. More info on this can be found in this white paper https://www.oracle.com/technetwork/database/features/bi-datawarehousing/twp-sql-plan-management-11gr2-133099.pdf

Reply
Bruce McCartney
April 6, 2011 1:44 pm

Hi, here is an updated link. Maria’s didn’t work… Here is one that works now:
https://www.oracle.com/technetwork/database/focus-areas/bi-datawarehousing/twp-sql-plan-management-11gr2-133099.pdf

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *