Restrict the usage of new execution plans with SQL Plan Baselines

We have several new features in the 11g version related to change management. One of them is SQL Plan Baselining. That means that you intend a very  conservative approach regarding the usage of new execution plans computed by the optimizer. In other words: You suspect that after a change, the newly computed execution plans may be bad and lead to a performance degradation.

Before the introduction of SQL Plan Baselines, you where able to fix the ‚good old execution plans‘ with hints or with stored outlines. Both have the disadvantage that they make it impossible for the optimizer to come up with even better execution plans than the ‚good old‘ ones. By using SQL Plan Baselines, you let the optimizer compute the new plans and you store them, with the possibility to later on test them and allow them for usage, if they have proved to be good. That  process is called evolvement.

I have developed a little scenario to demonstrate that. First we see a statement with the ‚good old‘ execution plan of ACCESS BY INDEX ROWID:

SQL> select channel_id,count(*) from sales group by channel_id;

CHANNEL_ID   COUNT(*)
---------- ----------
 1          1
 2    2064200
 4     947328
 3    4322623
 9      16592

SQL> create  bitmap index sales_bix on sales (channel_id) nologging;

Index created.

SQL> begin
       dbms_stats.gather_table_stats('ADAM','SALES',cascade=>true,
       method_opt=>'FOR ALL INDEXED COLUMNS SIZE 5');
     end;
     /

SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 1232.16

Execution Plan
----------------------------------------------------------
Plan hash value: 3388944652

-----------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |         |     1 |     8 |  1107   (0)| 00:00:14 |
|   1 |  SORT AGGREGATE               |         |     1 |     8 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID | SALES   |  5774 | 46192 |  1107   (0)| 00:00:14 |
|   3 |    BITMAP CONVERSION TO ROWIDS|         |       |       |            |          |
|*  4 |     BITMAP INDEX SINGLE VALUE | SALES_BIX       |       |            |          |
-----------------------------------------------------------------------------------------

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

 4 - access("CHANNEL_ID"=1)

Now I want to create a SQL Baseline for that statement with that execution plan.

One possibility to create it is the following:

SQL> show parameter baseline

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_capture_sql_plan_baselines boolean     FALSE
optimizer_use_sql_plan_baselines     boolean     TRUE

SQL> select * from dba_sql_plan_baselines;

no rows selected

SQL> alter session set optimizer_capture_sql_plan_baselines=true;

Session altered.

The following (repeating) statements in this session are getting recorded together with their execution plans:

SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 1232.16

SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 1232.16

SQL> alter session set optimizer_capture_sql_plan_baselines=false;

Session altered.

Following shows that there was one statement with one plan was recorded. This one plan

(the index access) is the only accepted one.

SQL> column sql_text format a29
SQL> select SQL_TEXT,SQL_HANDLE,PLAN_NAME,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines;

SQL_TEXT                      SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
----------------------------- ------------------------------ ------------------------------ --- --- ---
select sum(amount_sold) from  SYS_SQL_ec28978ecd0909c3       SYS_SQL_PLAN_cd0909c333c3099a  YES YES NO
sales where channel_id=1

Now we change the conditions, so that a Full Table Scan becomes attractive to the optimizer:

SQL> alter table sales nologging;
SQL> insert /*+ append */ into sales select PROD_ID,CUST_ID,TIME_ID,1,PROMO_ID,QUANTITY_SOLD,AMOUNT_SOLD from sales;

7350744 rows created.

SQL> commit;

Commit complete.

SQL> delete from sales where channel_id=3;

4322623 rows deleted.

SQL> commit;

Commit complete.

SQL> alter table sales enable row movement;

Table altered.

SQL> alter table sales shrink space cascade;

Table altered.

SQL> begin dbms_stats.gather_table_stats('ADAM','SALES',cascade=>true,
       method_opt=>'FOR ALL INDEXED COLUMNS SIZE 5');
     end;
     /

PL/SQL procedure successfully completed.

The majority of rows now has the channel_id 1.  The optimizer would like to do FTS now, but the baseline restricts it to the usage of the ‚good old‘ index access:

SQL> set autotrace on explain

SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 785647882

Execution Plan
----------------------------------------------------------
Plan hash value: 3388944652

-------------------------------------------------------------------------------------------
| Id  | Operation                     | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |           |     1 |     8 | 59594   (1)| 00:11:56 |
|   1 |  SORT AGGREGATE               |           |     1 |     8 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID | SALES     |  7340K|    56M| 59594   (1)| 00:11:56 |
|   3 |    BITMAP CONVERSION TO ROWIDS|           |       |       |            |          |
|*  4 |     BITMAP INDEX SINGLE VALUE | SALES_BIX |       |       |            |          |
-------------------------------------------------------------------------------------------

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

 4 - access("CHANNEL_ID"=1)

Note
-----
 - SQL plan baseline "SYS_SQL_PLAN_cd0909c333c3099a" used for this statement

The new plan, however, got recorded, but not accepted:

SQL> select SQL_TEXT,SQL_HANDLE,PLAN_NAME,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines;

SQL_TEXT                      SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
----------------------------- ------------------------------ ------------------------------ --- --- ---
select sum(amount_sold) from  SYS_SQL_ec28978ecd0909c3       SYS_SQL_PLAN_cd0909c333c3099a  YES YES NO
sales where channel_id=1

select sum(amount_sold) from  SYS_SQL_ec28978ecd0909c3       SYS_SQL_PLAN_cd0909c335032dee  YES  NO NO
sales where channel_id=1

An evolvement has to prove a strong improvement compared to the old

plan in order to get the new plan accepted for usage by the optimizer:

set serverout on
DECLARE
 report clob;
BEGIN
 report := DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE (
 sql_handle => 'SYS_SQL_ec28978ecd0909c3',
 plan_name=>'SYS_SQL_PLAN_cd0909c335032dee'
 );
 dbms_output.put_line(report);
END;
/

-------------------------------------------------------------------------------
 Evolve SQL Plan Baseline Report
-------------------------------------------------------------------------------

Inputs:
-------
 SQL_HANDLE = SYS_SQL_ec28978ecd0909c3
 PLAN_NAME  = SYS_SQL_PLAN_cd0909c335032dee
 TIME_LIMIT = DBMS_SPM.AUTO_LIMIT
 VERIFY     = YES
 COMMIT     = YES

Plan: SYS_SQL_PLAN_cd0909c335032dee
-----------------------------------
 Plan was verified: Time used 14.7 seconds.
 Failed performance criterion: Compound improvement ratio <= 1.01.

 Baseline Plan      Test Plan     Improv. Ratio
 -------------      ---------     -------------
 Execution Status:        COMPLETE       COMPLETE
 Rows Processed:                 1              1
 Elapsed Time(ms):           10883           3750               2.9
 CPUTime(ms):                 9697           2959              3.28
 Buffer Gets:                49997          50091                 1
 Disk Reads:                 46911          47217               .99
 Direct Writes:                  0              0
 Fetches:                    46911           1089             43.08
 Executions:                     1              1

-------------------------------------------------------------------------------
 Report Summary
-------------------------------------------------------------------------------
Number of SQL
plan baselines verified: 1.
Number of SQL plan baselines evolved: 0.

PL/SQL procedure successfully completed.

Although an improvement is visible, it is not big enough to lead to acceptance of the

new plan. This SQL Plan Baseline approach is really conservative.

But we can force the acceptance:

set serverout on
DECLARE
 report clob;
BEGIN
 report := DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE (
 sql_handle => 'SYS_SQL_ec28978ecd0909c3',
 plan_name=>'SYS_SQL_PLAN_cd0909c335032dee',
 verify=>'NO'
 );
 dbms_output.put_line(report);
END;
/
-------------------------------------------------------------------------------
 Evolve SQL Plan Baseline Report
-------------------------------------------------------------------------------

Inputs:
-------
 SQL_HANDLE = SYS_SQL_ec28978ecd0909c3
 PLAN_NAME  = SYS_SQL_PLAN_cd0909c335032dee
 TIME_LIMIT = DBMS_SPM.AUTO_LIMIT
 VERIFY     = NO
 COMMIT     = YES

Plan: SYS_SQL_PLAN_cd0909c335032dee
-----------------------------------
 Plan was changed to an accepted plan.

-------------------------------------------------------------------------------
 Report Summary
-------------------------------------------------------------------------------
Number of SQL plan baselines verified: 0.
Number of SQL plan baselines evolved: 1.

PL/SQL procedure successfully completed.

Now the optimizer is free to choose this accepted plan – or the other accepted ones.

SQL> select SQL_TEXT,SQL_HANDLE,PLAN_NAME,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines;

SQL_TEXT                      SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
----------------------------- ------------------------------ ------------------------------ --- --- ---
select sum(amount_sold) from  SYS_SQL_ec28978ecd0909c3       SYS_SQL_PLAN_cd0909c333c3099a  YES YES NO
sales where channel_id=1

select sum(amount_sold) from  SYS_SQL_ec28978ecd0909c3       SYS_SQL_PLAN_cd0909c335032dee  YES YES NO
sales where channel_id=1

SQL> set autotrace on explain
SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 785647882

Execution Plan
----------------------------------------------------------
Plan hash value: 1047182207

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |     8 | 13795   (2)| 00:02:46 |
|   1 |  SORT AGGREGATE    |       |     1 |     8 |            |          |
|*  2 |   TABLE ACCESS FULL| SALES |  7340K|    56M| 13795   (2)| 00:02:46 |
----------------------------------------------------------------------------

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

 2 - filter("CHANNEL_ID"=1)

Note
-----
 - SQL plan baseline "SYS_SQL_PLAN_cd0909c335032dee" used for this statement

We can even fix one execution plan – for example for runtime comparison.

First we demand index access:

declare
 numplans pls_integer;
begin
 numplans:=
 DBMS_SPM.ALTER_SQL_PLAN_BASELINE (
 sql_handle=>'SYS_SQL_ec28978ecd0909c3',
 plan_name=>'SYS_SQL_PLAN_cd0909c333c3099a',
 attribute_name=>'FIXED',
 attribute_value=>'YES');
 dbms_output.put_line(numplans);
end;
/

SQL> set autotrace on explain
SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 785647882

Execution Plan
----------------------------------------------------------
Plan hash value: 3388944652

-------------------------------------------------------------------------------------------
| Id  | Operation                     | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |           |     1 |     8 | 59594   (1)| 00:11:56 |
|   1 |  SORT AGGREGATE               |           |     1 |     8 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID | SALES     |  7340K|    56M| 59594   (1)| 00:11:56 |
|   3 |    BITMAP CONVERSION TO ROWIDS|           |       |       |            |          |
|*  4 |     BITMAP INDEX SINGLE VALUE | SALES_BIX |       |       |            |          |
-------------------------------------------------------------------------------------------

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

 4 - access("CHANNEL_ID"=1)

Note
-----
 - SQL plan baseline "SYS_SQL_PLAN_cd0909c333c3099a" used for this statement

SQL> set autotrace off
SQL> set timing on
SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 785647882

Elapsed: 00:00:07.08

Now we demand Full Table Scan:

SQL> declare
 2     numplans pls_integer;
begin
 numplans:=
 DBMS_SPM.ALTER_SQL_PLAN_BASELINE (
 sql_handle=>'SYS_SQL_ec28978ecd0909c3',
 plan_name=>'SYS_SQL_PLAN_cd0909c333c3099a',
 attribute_name=>'FIXED',
 attribute_value=>'NO');
 dbms_output.put_line(numplans);
end;
/  3    4    5    6    7    8    9   10   11   12
1

PL/SQL procedure successfully completed.

declare
 numplans pls_integer;
begin
 numplans:=
 DBMS_SPM.ALTER_SQL_PLAN_BASELINE (
 sql_handle=>'SYS_SQL_ec28978ecd0909c3',
 plan_name=>'SYS_SQL_PLAN_cd0909c335032dee',
 attribute_name=>'FIXED',
 attribute_value=>'YES');
 dbms_output.put_line(numplans);
end;
/

SQL> select sum(amount_sold) from sales where channel_id=1;

SUM(AMOUNT_SOLD)
----------------
 785647882

Elapsed: 00:00:02.04

As you see, the new execution plan is really faster in this case – it was the right decision to allow its usage.But first, we saw a proof of it during the evolvement. So we did not trust the optimizer blindly. We had a strong control over the change process!

Addendum: Please see this excellent series of Articles regarding SQL Plan Baselines from the Optimizer Development Group:

http://optimizermagic.blogspot.com/2009/01/plan-regressions-got-you-down-sql-plan.html

http://optimizermagic.blogspot.com/2009/01/sql-plan-management-part-2-of-4-spm.html

http://optimizermagic.blogspot.com/2009/01/sql-plan-management-part-3-of-4.html

http://optimizermagic.blogspot.com/2009/02/sql-plan-management-part-4-of-4-user.html

Second Addendum: Migrate Stored Outlines to SQL Plan Baselines – Why & How

http://blogs.oracle.com/optimizer/entry/how_do_i_migrate_stored

,

  1. #1 von Azhar Syed am Juli 21, 2014 - 18:44

    Uwe Thanks for this post.. However I had a query regarding Baselines … What about a query that runs for both a very small amount of data and a very large amount of data.If the baseline is set for large amount of data then when we run for a small amount of data it will take minutes instead of seconds. The other way around (set the baseline for small and then run large) could make the large query go from hours to days. Is this possible and if it is so , is there anyway that this can be avoided ?

  1. OUGN Spring Seminar Day 1 « The Oracle Instructor
  2. OUGN Spring Seminar Day 1 | Oracle Administrators Blog - by Aman Sood

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..