Sunday, March 6, 2022

Step By Step Guide of How to Use SQL Plan Baseline

 Introduction:

In this topic I'll discuss the technical steps of using SQL Plan Baseline (SPM) which its main rule is to pick the best execution plan for SQL statements.

Before I dive into technical details, let me shed some light on SQL Plan Baseline history:

In Oracle 11g Oracle introduced SQL Plan Management as a new feature to manage the execution plan to use only best plans, but Oracle started this effort since Oracle 8i with "Stored Outlines" which can help in fixing "hints" --not the plan itself to a SQL statement. In Oracle 10g, Oracle introduced "SQL Profile" to fix the plan instead of fixing the hint and this worked fine especially if the DBA used a tool like SQLT to create a SQL Profile and copy this profile from one database to another, although Oracle was saying that SQL Profile will choose the best plan among existing plans, but that was not the case all the time, many plans that get chosen by SQL Profile can bring the server on its knees.

In Oracle 11g, Oracle tried to fix this gap by introducing SPM which collects all the execution plans of a statement and classify them into two pools (accepted plans and unaccepted plans) by default any new plan will be added automatically to the Unaccepted plans pool until it get vetted by a process called Evolution process, this process will examine the unaccepted plans and move the ones with the lowest cost from "unaccepted pool" to the "accepted pool". The question here, which plan will be finally used by the optimizer? The optimizer will use the best plan from the "Accepted pool" which is having the lowest cost.

What is your role as a DBA in this complicated process?

1- First, you have to decide whether SQL Plan Baseline will help you with stabilizing your environment or not. From my personal perspective, I used to use SQL Plan Baseline only during the migrations from 11g to higher versions to stabilize the plans in order to avoid having original SQLs to pick wrong plans in the new version.
I believe Oracle needs to invest more in this feature in order to make it mature enough to be relied on.

2- If you decide to use SQL Plan Baseline, then you have to enable it, because it's disabled by default.

3- You have to maintain the accuracy of SQL Plan Baseline by evolving the unaccepted plans or manually fix best plans inside the baseline.

Now, let's jump to the technical steps of how to use SPM:

Enabling SQL Plan BASELINE:

Set the parameter optimizer_capture_sql_plan_baselines to TRUE to start capturing Execution Plans for (Hard Parsed) SQL Statements, it should be enabled long enough to cover most or all of the application activities which varies from one application to another, once you feel that most statements got captured you can set it back to FALSE.

Notes:
- Enable this parameter only when you are sure that the current plans are performing well.
- As long as you keep optimizer_capture_sql_plan_baselines parameter enabled you should closely monitor the SYSAUX tablespace because it will get inflated as more plans will get stored into it.

To ENABLE the Execution Plan capturing for ALL SQL Statements
    SQL>     alter system set optimizer_capture_sql_plan_baselines = true;

-- You can enable SQL Baseline for Specific Statement instead of enabling it database wide: [Without the need of setting optimizer_capture_sql_plan_baselines to TRUE]
    SQL>     SET SERVEROUTPUT ON
        DECLARE
          l_plans_loaded  PLS_INTEGER;
        BEGIN
            l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
            sql_id => '
gat6z1bc6nc2d'); 
          DBMS_OUTPUT.put_line('Plans Loaded: ' || l_plans_loaded);
        END;
        /


Notes:
- It's recommended to flush the SHARED POOL to enforce the hard parse of SQL Statements to be considered in the BASELINE:
    SQL>   
alter system flush shared_pool;

- Baseline will capture only the statements that been executed at least twice.

-
optimizer_capture_sql_plan_baselines parameter should be set to TRUE until you feel that all application workload statements have ran twice or Hard parsed.

- Statements with outlines will not be considered in the Baseline if the parameter
use_stored_outlines is set to TRUE.

- In case you want to use SQL Plan Baseline for upgrade purpose but you didn't enable the plan capturing long enough before the upgrade, you still can make use of SPM to stabilize the plans; after the upgrade you can set optimizer_features_enable to the old DB version and set
optimizer_capture_sql_plan_baselines = true for sometime till you feel that all application statements have executed twice to be considered in the Plan Baseline then turn it back to False and reset optimizer_features_enable parameter back to the current DB version.

Later to make sure that the Optimizer will use SQL Plan BASELINE all the time instead of using new plans, make sure the optimizer_use_sql_plan_baselines parameter is set to TRUE all the time:

Using SQL Plan BASELINE:

SPM aware optimizer should be enabled to use SQL Plan BASELINES: [Default Enabled]
    SQL>     alter system set optimizer_use_sql_plan_baselines = true;        --Default

Note:
The following line at the end of any SQL statement execution plan indicates that SQL Baseline was used for this statement.   
SQL> select * from table(dbms_xplan.display_cursor('&SQLID', 0, 'basic note'));
...
Note
-----
- SQL plan baseline SYS_SQL_PLAN_fcc170b0a62d0f4d used for this statement


-- Display all plans for SQL Statements that are inserted to the SQL Plan BASELINE:
    SQL>     SET LONG 10000 pages 1000 lines 170
        SELECT SQL_HANDLE,sql_text, plan_name, enabled, accepted from dba_sql_plan_baselines
        where sql_text NOT LIKE '%dba_sql_plan_baselines%'
        and sql_text like '%DELETE FROM RESERVATION_SEAT_INFO WHERE RESERVATION_SEAT_INFO.ITEMID%';
    Or:

    -- For a specific SQL statement:
    SQL>     SET LONG 10000
                  SELECT * FROM TABLE(DBMS_XPLAN.display_sql_plan_baseline(plan_name=>'SYS_SQL_PLAN_d90440b9ed3324c0'));


Evolve the SQL Plan BASELINE to consider new plans:

As far optimizer_use_sql_plan_baselines parameter is set to TRUE, new plans will be added to the BASELINE but will not be accepted.
In case you want SQL Plan BASELINE to re-evaluate/consider the new plans if their cost is LOWER, you have to evolve it manually:

-- Evolve For ALL SQL statements in the BASELINE:
    SQL>    SET LONG 10000
        SELECT DBMS_SPM.evolve_sql_plan_baseline() FROM dual;


-- Evolve For Specific SQL statements: using its sql_handle:
    SQL>    SET LONG 10000
        SELECT DBMS_SPM.evolve_sql_plan_baseline(sql_handle => 'SYS_SQL_7b76323ad90440b9') FROM dual;

        Or
        --
        By Using SQL Tuning Advisor
        SQL> var tname varchar2(30);
        SQL> exec :tname := dbms_sqltune.create_tuning_task(sql_id => 'bfbr3zrg9d5cc');
        SQL> exec dbms_sqltune.execute_tuning_task(task_name => :tname);
        SQL> select dbms_sqltune.report_tuning_task(:tname, 'TEXT', 'BASIC') FROM dual;


 In 12c:
 
 - The evolve task is automated under automated maintenance task "SYS_AUTO_SPM_EVOLVE_TASK" | As far SQL Tune Advisor is enabled.
 - To display the actions taken by the last run of that task:
    SQL>    SET LONG 1000000 PAGESIZE 1000 LONGCHUNKSIZE 100 LINESIZE 100
        SELECT DBMS_SPM.report_auto_evolve_task FROM   dual;

 - Due to auto-evolve SPM may accept new least cost plans automatically, If you don't want this to happen:
    SQL>    BEGIN
        DBMS_SPM.set_evolve_task_parameter(
        task_name => 'SYS_AUTO_SPM_EVOLVE_TASK',
        parameter => 'ACCEPT_PLANS',
        value     => 'FALSE');
        END;
        /

Manually FIX a specific plan from the Baseline:

In case you want to fix a specific plan manually regardless of Evolve result: [Provide sql_handle & plan_name]
    SQL>     select SQL_HANDLE,sql_text, plan_name, enabled, accepted from dba_sql_plan_baselines where sql_text NOT LIKE '%dba_sql_plan_baselines%';

    SQL>     SET SERVEROUTPUT ON
        DECLARE
          l_plans_altered  PLS_INTEGER;
        BEGIN
          l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
            sql_handle      => '
SYS_SQL_7b76323ad90440b9',
            plan_name       => '
SYS_SQL_PLAN_d90440b9ed3324c0',
            attribute_name  => '
fixed',
            attribute_value => '
YES');

          DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
        END;
        /

Fix all plans inside the Baseline:

Now the plans are loaded to the baselines but still, the optimizer can add more plans and use them in case it finds better plans.
In case you want to force the optimizer to use the plans inside the baseline regardless, they are good or bad, you can fix them using this command:
SQL> begin
      for i in (select rownum n,plan_name from dba_sql_plan_baselines) loop
       dbms_output.put_line(dbms_spm.alter_sql_plan_baseline(plan_name=>i.plan_name,attribute_name=>'FIXED',attribute_value=>'YES'));
      end loop;
     end;
     /

SQL> select SQL_HANDLE,ENABLED,ACCEPTED,FIXED from DBA_SQL_PLAN_BASELINES;

Or simply: Switch OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES to FALSE and it won't add more plans to the Baseline and will use whatever exists inside the Baselines:

SQL>  alter system set OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=FALSE;

Manually DROP a specific plan from the Baseline:

-- If plan_name is NULL it will drop all BASELINE plans:
    SQL>    SET SERVEROUTPUT ON
        DECLARE
        l_plans_dropped  PLS_INTEGER;
        BEGIN
        l_plans_dropped := DBMS_SPM.drop_sql_plan_baseline (
        sql_handle => NULL,
        plan_name  => 'SYS_SQL_7b76323ad90440b9');
    
        DBMS_OUTPUT.put_line(l_plans_dropped);
        END;
        /


Manually DISABLE a plan:

    SQL>     variable cnt number;
    SQL>     exec :cnt :=DBMS_SPM.ALTER_SQL_PLAN_BASELINE(
        SQL_HANDLE     => 'SYS_SQL_bf5c9b08f72bde3e',
        PLAN_NAME    => 'SQL_PLAN_byr4v13vkrrjy42949306',
        ATTRIBUTE_NAME     => 'enabled',
        ATTRIBUTE_VALUE => 'NO');

    -- Check the plan status:
    SQL>     SELECT sql_handle, sql_text, plan_name, enabled FROM dba_sql_plan_baselines;


Maintaning SQL Plan Baseline impact:

-- Check how much space consumed by SPM on SYSAUX tbs:
    SQL>     SELECT  occupant_desc, space_usage_kbytes FROM  v$sysaux_occupants WHERE   occupant_name='SQL_MANAGEMENT_BASE';

    SQL>    SELECT  * FROM (
        SELECT  bytes/(1024*1024) MBytes, tablespace_name, segment_name, segment_type
        FROM    dba_segments WHERE owner = 'SYS' ORDER BY bytes DESC)
        WHERE   rownum <= 20;

-- Control the space used by SPM as a percentage of SYSAUX tablespace total size:
    SQL>    SELECT * FROM   dba_sql_management_config;
        BEGIN
          DBMS_SPM.configure('space_budget_percent', 11);    --SPM to use 11% of SYSAUX tablespace size [Default is 10%]
        END;
        /


-- Control the Purge of un-used SPM plans:
    SQL>    SELECT * FROM   dba_sql_management_config;
    -- set the retention of UN-used Plans first in weeks [After which the un-used plans will be purged]:
    SQL>    EXECUTE DBMS_SPM.CONFIGURE('plan_retention_weeks',10);

-- Run the purge manually:
    SQL>    VARIABLE ret NUMBER;
        BEGIN
         :ret := sys.dbms_spm_internal.auto_purge_sql_plan_baseline;
        END;
        /
        PRINT ret;



-- Identifying When Baselines were Last Used:
    SQL>     SELECT FLOOR(EXTRACT(DAY FROM (SYSDATE - last_executed)) / 7) AS "Weeks Since Last Execute",
               COUNT(*) AS "Num Baselines"
        FROM   dba_sql_plan_baselines
        WHERE  accepted = 'YES'
        AND    autopurge = 'YES'
        GROUP BY FLOOR(EXTRACT(DAY FROM (SYSDATE - last_executed)) / 7)
        ORDER BY 1;

Misc SPM related tasks:
----
    -- Create SQL Plan Baseline One Time only: [From Cursor Cache]
    SQL>    variable pls number;
        exec :pls := dbms_spm.load_plans_from_cursor_cache( attribute_name => 'SQL_TEXT', attribute_value => 'select%p.prod_name%');


Transfer SQL Plan Baseline between databases:


-- Create Staging table: [Source DB]
    SQL>     BEGIN
          DBMS_SPM.CREATE_STGTAB_BASELINE(
            table_name      => 'SPM_STAGING_TAB',
            table_owner     => 'SCOTT',
            tablespace_name => 'USERS');
        END;
        /

-- Populate the staging table with SQL plan Baselines: [Source DB]
    SQL>    SET SERVEROUTPUT ON
        DECLARE
          l_plans_packed  PLS_INTEGER;
        BEGIN
            l_plans_packed := DBMS_SPM.pack_stgtab_baseline(
            table_name      => 'SPM_STAGING_TAB',
            table_owner     => 'SCOTT');
    
          DBMS_OUTPUT.put_line('Plans Packed: ' || l_plans_packed);
        END;
        /

-- Export the table:  [Source DB]
#expdp \' / as sysdba \' tables=SCOTT.SPM_STAGING_TAB directory=EXPORT_FILES_DBA_BUNDLE dumpfile=SPM_STAGING_TAB.dmp logfile=SPM_STAGING_TAB.log REUSE_DUMPFILES=Y

-- Import the table:  [Target DB]
# impdp \'/ as sysdba\' tables=SCOTT.SPM_STAGING_TAB directory=EXPORT_FILES_DBA_BUNDLE dumpfile=SPM_STAGING_TAB.dmp logfile=IMP_SPM_STAGING_TAB.log

-- Load SQL Plan Baselines into Dictionary: [Target DB]
    SQL>     SET SERVEROUTPUT ON
        DECLARE
          l_plans_unpacked  PLS_INTEGER;
        BEGIN
          l_plans_unpacked := DBMS_SPM.unpack_stgtab_baseline(
            table_name      => 'SPM_STAGING_TAB',
            table_owner     => 'SCOTT',
            creator         => 'SCOTT');

          DBMS_OUTPUT.put_line('Plans Unpacked: ' || l_plans_unpacked);
        END;
        /

Bonus:

You can use this script to Create a new Baseline, Fix a plan, Drop a plan for a specific SQL Statement:

http://dba-tips.blogspot.com/2020/10/sql-tuning-script.html


References:     

https://oracle-base.com/articles/11g/sql-plan-management-11gr1
https://oracle-base.com/articles/12c/adaptive-sql-plan-management-12cr1
https://blogs.oracle.com/optimizer/sql-plan-management-part-1-of-4-creating-sql-plan-baselines
https://blogs.oracle.com/optimizer/sql-plan-management-part-2-of-4-spm-aware-optimizer
https://blogs.oracle.com/optimizer/sql-plan-management-part-3-of-4:-evolving-sql-plan-baselines
https://blogs.oracle.com/optimizer/does-the-use-of-sql-plan-management-and-the-dbmsspm-database-package-require-a-tuning-or-diagnostic-pack-license
http://www.oracle.com/technetwork/database/bi-datawarehousing/twp-sql-plan-management-0218-4403742.pdf



1 comment: