Tuesday, December 29, 2015

Fixing execution plans using SQL plan baselines

This post shows how to use SQL plan baselines to fix execution plan of a query. This can be used as a replacement for stored outlines.

For this demonstration we create 2 tables each with a row and collect statistics on them.


create  table tab1 (a number, b varchar2(10));
insert into tab1 values(10, 'AA');
create index tab1_i1 on tab1(a);

create  table tab2 (a number, c varchar2(10));
create index tab2_i1 on tab2(a);
insert into tab2 values(10, 'BBB');

exec dbms_stats.gather_table_stats (ownname=>'MURTY', tabname=>'TAB1', estimate_percent=>10, METHOD_OPT=>'FOR ALL COLUMNS SIZE AUTO', cascade=>true);
exec dbms_stats.gather_table_stats (ownname=>'MURTY', tabname=>'TAB2', estimate_percent=>10, METHOD_OPT=>'FOR ALL COLUMNS SIZE AUTO', cascade=>true);

Here is the query for which we will change execution plan using SQL plan baselines.


select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

The execution plan shows nested loop join


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3684952675

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |    13 |     4   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |         |     1 |    13 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | TAB1    |     1 |     6 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | TAB2_I1 |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |     1 |     7 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   4 - access("TAB1"."A"="TAB2"."A")

17 rows selected.

If the parameter optimizer_capture_sql_plan_baselines is set to true, after multiple executions of the query, it will create a baseline automatically.


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
Plan hash value: 3684952675

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |    13 |     4   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |         |     1 |    13 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | TAB1    |     1 |     6 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | TAB2_I1 |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |     1 |     7 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   4 - access("TAB1"."A"="TAB2"."A")

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

21 rows selected.

If the parameter optimizer_capture_sql_plan_baselines is set to false, we can create a SQL plan baseline manually using SQL_ID of statement.


SQL> select sql_id, sql_text from v$sql where sql_text like 'select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a';

SQL_ID
-------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
bqn8dqudd4ajf
select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a

set serveroutput on
declare
  i pls_integer;
BEGIN
  i := dbms_spm.load_plans_from_cursor_cache(sql_id => 'bqn8dqudd4ajf');
  dbms_output.put_line('Plans Loaded: ' || i);
END;
/
Plans Loaded: 1

PL/SQL procedure successfully completed.

Details of SQL Plan baselines can be viewed from dictionary view dba_sql_plan_baselines


SQL> select SQL_HANDLE, SQL_TEXT, PLAN_NAME from dba_sql_plan_baselines where PLAN_NAME='SQL_PLAN_2ppbwajdruq05fc48919a';

SQL_HANDLE                     SQL_TEXT                                                                         PLAN_NAME
------------------------------ -------------------------------------------------------------------------------- ------------------------------
SQL_2ad57c545b7d5805           select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a                SQL_PLAN_2ppbwajdruq05fc48919a

And more details can be get like below using sql_handle


SQL>  select * from table(dbms_xplan.display_sql_plan_baseline( sql_handle=>'SQL_2ad57c545b7d5805', format=>'basic'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
SQL handle: SQL_2ad57c545b7d5805
SQL text: select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05fc48919a         Plan id: 4232614298
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 3684952675

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  NESTED LOOPS                |         |
|   2 |   NESTED LOOPS               |         |
|   3 |    TABLE ACCESS FULL         | TAB1    |
|   4 |    INDEX RANGE SCAN          | TAB2_I1 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |
------------------------------------------------

23 rows selected.

Now we will try to force sort merge join for the same SQL using hint USE_MERGE


SQL> explain plan for select /*+ USE_MERGE(tab1 tab2) */ tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1351227119

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     6  (17)| 00:00:01 |
|   1 |  MERGE JOIN                  |         |     1 |    13 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |     1 |     6 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |     1 |       |     1   (0)| 00:00:01 |
|*  4 |   SORT JOIN                  |         |     1 |     7 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | TAB2    |     1 |     7 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   4 - access("TAB1"."A"="TAB2"."A")
       filter("TAB1"."A"="TAB2"."A")

18 rows selected.

Execute once to get details in v$sql_plan, and get SQL_ID


SQL> select /*+ USE_MERGE(tab1 tab2) */ tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

         A B          C
---------- ---------- ----------
        10 AA         BBB
        
SQL> select distinct SQL_ID from v$sql_plan where PLAN_HASH_VALUE=1351227119;

SQL_ID
-------------
fqjpqc2t0byr0

Now we will add the plan that uses sort merge join to SQL plan baseline. So, without using any hint, the execution of query uses sort merge join. Here we are using sql_handle of baseline created for original query, but sql_id and plan_hash_value of hinted query.


set serveroutput on
declare
  i pls_integer;
BEGIN
  i := dbms_spm.load_plans_from_cursor_cache(sql_id => 'fqjpqc2t0byr0', plan_hash_value => 1351227119, sql_handle => 'SQL_2ad57c545b7d5805');
  dbms_output.put_line('Plans Loaded: ' || i);
END;
/
Plans Loaded: 1

PL/SQL procedure successfully completed.

Baseline of original query now shows 2 plans.


SQL> select * from table(dbms_xplan.display_sql_plan_baseline( sql_handle=>'SQL_2ad57c545b7d5805', format=>'basic'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
SQL handle: SQL_2ad57c545b7d5805
SQL text: select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05e3fe5496         Plan id: 3825095830
Enabled: YES     Fixed: NO      Accepted: YES     Origin: MANUAL-LOAD
--------------------------------------------------------------------------------

Plan hash value: 1351227119

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  MERGE JOIN                  |         |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |
|   4 |   SORT JOIN                  |         |
|   5 |    TABLE ACCESS FULL         | TAB2    |
------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05fc48919a         Plan id: 4232614298
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE 
--------------------------------------------------------------------------------

Plan hash value: 3684952675

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  NESTED LOOPS                |         |
|   2 |   NESTED LOOPS               |         |
|   3 |    TABLE ACCESS FULL         | TAB1    |
|   4 |    INDEX RANGE SCAN          | TAB2_I1 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |
------------------------------------------------

41 rows selected.

But still we see nested loop join plan only for the query.


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3684952675

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |    13 |     4   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |         |     1 |    13 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | TAB1    |     1 |     6 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | TAB2_I1 |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |     1 |     7 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   4 - access("TAB1"."A"="TAB2"."A")

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

21 rows selected.

We will make sort-merge join plan as fixed, so it always uses that fixed execution plan.


declare
  i  pls_integer;
begin
  i := dbms_spm.alter_sql_plan_baseline(sql_handle=>'SQL_2ad57c545b7d5805', plan_name=>'SQL_PLAN_2ppbwajdruq05e3fe5496', attribute_name=>'FIXED', attribute_value=>'YES');
  dbms_output.put_line('Plans altered: ' || i);
end;
/
Plans altered: 1

PL/SQL procedure successfully completed.

Details of baseline now show that sort-merge plan as fixed.


SQL> select * from table(dbms_xplan.display_sql_plan_baseline( sql_handle=>'SQL_2ad57c545b7d5805', format=>'basic'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
SQL handle: SQL_2ad57c545b7d5805
SQL text: select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05e3fe5496         Plan id: 3825095830
Enabled: YES     Fixed: YES     Accepted: YES     Origin: MANUAL-LOAD
--------------------------------------------------------------------------------

Plan hash value: 1351227119

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  MERGE JOIN                  |         |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |
|   4 |   SORT JOIN                  |         |
|   5 |    TABLE ACCESS FULL         | TAB2    |
------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05fc48919a         Plan id: 4232614298
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 3684952675

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  NESTED LOOPS                |         |
|   2 |   NESTED LOOPS               |         |
|   3 |    TABLE ACCESS FULL         | TAB1    |
|   4 |    INDEX RANGE SCAN          | TAB2_I1 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |
------------------------------------------------

41 rows selected.

So, we can get execution plan with sort-merge without any hints.


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------
Plan hash value: 1351227119

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     6  (17)| 00:00:01 |
|   1 |  MERGE JOIN                  |         |     1 |    13 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |     1 |     6 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |     1 |       |     1   (0)| 00:00:01 |
|*  4 |   SORT JOIN                  |         |     1 |     7 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | TAB2    |     1 |     7 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   4 - access("TAB1"."A"="TAB2"."A")
       filter("TAB1"."A"="TAB2"."A")

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

22 rows selected.

If we do not like to use plans from SQL Plan baseline, we can set optimizer_use_sql_plan_baselines parameter as false. Also, if we want to drop baseline, we can do it like below.


declare
  i  pls_integer;
begin
  i :=  dbms_spm.drop_sql_plan_baseline(sql_handle=>'SQL_2ad57c545b7d5805',plan_name=>NULL);
  dbms_output.put_line('Plans removed: ' || i);
end;
/
Plans removed: 2

PL/SQL procedure successfully completed.