Oracle SQL Plan Management – Part 3

SQL Plan Management – Oracle Database 11g

You have seen the first 2 parts of SQL plan baselines

Part 1 – https://avdeo.com/2011/06/02/oracle-sql-plan-management-part-1/
Part 2 – https://avdeo.com/2011/06/07/oracle-sql-plan-management-%e2%80%93-part-2/

In part 1 we have seen information about SPM and how to automatically capture the baseline
In part 2 we have seen how to manually capture the baseline and the affect of using FIXED variable
In part 3 we will now see, how to import the baselines for SQL from some remote database and also how to set a specific plan for a query using hint and then creating baseline for that

Importing baseline from remote database to our database

Lets say we have a database and a table T in the database.

SQL> desc T
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 COL1                                               NUMBER
 COL2                                               VARCHAR2(50)
 COL3                                               DATE

No Index exists on this table.

SQL> select count(1) from T;

  COUNT(1)
----------
   2097156

SQL> select col1, count(1) from T group by col1;

      COL1   COUNT(1)
---------- ----------
         1          1
         2          3
         3    2097152

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  1048K|    11M|  1424   (6)| 00:00:18 |
|*  1 |  TABLE ACCESS FULL| T    |  1048K|    11M|  1424   (6)| 00:00:18 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

13 rows selected.

SQL>

Lets create a baseline for this query manually.

Find the SQL ID using

SQL>select sql_id from v$sql where sql_text = 'select * from T where col1 = 1';

SQL_ID
-------------
5pvxxjg6n7mrp

SQL>

DECLARE
  l_plans_loaded  PLS_INTEGER;
BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
    sql_id => '5pvxxjg6n7mrp');
END;
/

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID');
Enter value for sql_id: 5pvxxjg6n7mrp
old   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID')
new   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='5pvxxjg6n7mrp')

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO

SQL>

So now the baseline is created for this SQL.

If we check the plan it will show us Full Table Scan.

Now lets create an index.

SQL> create index T_IDX on T(col1);

Index created.

SQL> exec DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> '&owner', TABNAME => '&tabname', DEGREE => 6, GRANULARITY => 'ALL' ,CASCADE => TRUE , METHOD_OPT => 'FOR ALL INDEXED COLUMNS SIZE 254');
Enter value for owner: ADVAITD_DBA
Enter value for tabname: T

PL/SQL procedure successfully completed.

SQL>

lets see the plan for same query again. Since we have created an index on col1 of table T, when using value 1 we expect an index to be used rather than FTS.

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  1048K|    11M|  1424   (6)| 00:00:18 |
|*  1 |  TABLE ACCESS FULL| T    |  1048K|    11M|  1424   (6)| 00:00:18 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

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

17 rows selected.

SQL>

Here we clearly see baseline is getting used and it still uses FTS. This is a plan stability feature in 11g. No matter what changes we do, if a baseline already exists for a query and even if you create an index, it wont use that index unless you create a new baseline for that. As you know by now, new baseline will get created automatically only if you set OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES parameters is set to true. If this is set to false you have to create baseline manaully using DBMS_SPM package as shown above.

At this point of time we have an option to create a baseline and using DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE

But instead of using that we will try to export and import a baseline from some other DB having same query and tables.

So lets say, I have another database having same table and running same query. But that query is using correct index on col1. So we will see how to get that baseline exported and imported to our DB.
In real time situation, we will get several cases where in same set of queries are running good in some other databases but thay are having bad plan in our databaes. So instead of trying to get the plan right using traditional methods, we can simply export and import the baseline of that query from some other DB where its performing better and plans will be fixed in our DB without much effort.
So on my new DB, I have a correct plan as indicated below

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |  2359K|   139M|   307   (1)| 00:00:04 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  2359K|   139M|   307   (1)| 00:00:04 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  2379K|       |    47   (3)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

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

18 rows selected.

SQL>

Above query is using the baseline SYS_SQL_PLAN_1d83920fae82cf72

We will export and import this baseline in our database now. Following steps needs to be done to export and import the baseline

1) Create a staging table

exec DBMS_SPM.CREATE_STGTAB_BASELINE('STGTAB', 'ADVAITD_DBA');

STGTAB –> Name of staging table. You can give any name which is not used till now. A table with this name will get created.
ADVAITD_DBA –> Owner of the table. Table will get created in this schema.

2) Pack baseline into this table

declare
x number;
begin
x := DBMS_SPM.PACK_STGTAB_BASELINE('STGTAB', 'ADVAITD_DBA', sql_handle => 'SYS_SQL_1447ba3a1d83920f', plan_name => 'SYS_SQL_PLAN_1d83920fae82cf72' );
end;
/

3) Export the staging table from source DB where you have baseline packed
exp userid=advaitd_dba@new_db tables=’STGTAB’ file=STGTAB.dmp log=STGTAB.log

advaitd.desktop$ exp userid=advaitd_dba@new_db tables='STGTAB' file=STGTAB.dmp log=STGTAB.log

Export: Release 10.2.0.2.0 - Production on Sun Aug 7 06:15:39 2011

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Password:

Connected to: Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
Export done in US7ASCII character set and AL16UTF16 NCHAR character set
server uses UTF8 character set (possible charset conversion)

About to export specified tables via Conventional Path ...
. . exporting table                         STGTAB          1 rows exported
Export terminated successfully without warnings.
advaitd.desktop$

4) Import the table into the DB where you want to put the baseline
imp userid=advaitd_dba@our_db file=STGTAB.dmp log=STGTAB.log full=y

advaitd.desktop$ imp userid=advaitd_dba@our_db file=STGTAB.dmp log=STGTAB.log full=y

Import: Release 10.2.0.2.0 - Production on Sun Aug 7 06:20:54 2011

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Password:

Connected to: Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

Export file created by EXPORT:V10.02.01 via conventional path
import done in US7ASCII character set and AL16UTF16 NCHAR character set
import server uses UTF8 character set (possible charset conversion)
. importing ADVAITD_DBA's objects into ADVAITD_DBA
. importing ADVAITD_DBA's objects into ADVAITD_DBA
. . importing table                       "STGTAB"          1 rows imported
Import terminated successfully without warnings.
advaitd.desktop$

5) Unpack the staging table

declare
x number;
begin
x := DBMS_SPM.UNPACK_STGTAB_BASELINE('STGTAB', 'ADVAITD_DBA');
end;
/

Now we can see 2 baseline.
– One baseline which was already exisitng and doing FTS
– One we imported just now which is using index

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID');
Enter value for sql_id: 5pvxxjg6n7mrp
old   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID')
new   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='5pvxxjg6n7mrp')

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES YES NO

Now, if we check the plan for same query, we should be able to see index getting used and it will use the plan SYS_SQL_PLAN_1d83920fae82cf72 which we imported from other DB

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |  1048K|    11M|    46   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  1048K|    11M|    46   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  1048K|       |    21   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

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

18 rows selected.

SQL>

Creating baseline for user created plans

Lets go back to original situation where query on the table is doing a FTS. At this point instead of getting a profile from some other DB, lets create a correct profile using hints.

I am flushing the shared_pool so as to remove any exising plans from shared_pool

SQL> alter system flush shared_pool;

System altered.

SQL> /

System altered.

SQL>

Lets use Index hint here and see if the query uses index.

SQL> select /*+ index(T_IDX T) +*/ * from T where col1 = 1;

      COL1 COL2                                               COL3
---------- -------------------------------------------------- -----------
         1                                                    10-JUL-2011

SQL> explain plan for
  2  select /*+ index(T_IDX T) +*/ * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |  1048K|    11M|   465   (2)| 00:00:06 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  1048K|    11M|   465   (2)| 00:00:06 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  1048K|       |   209   (2)| 00:00:03 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

14 rows selected.

SQL>

So using a hint serve the purpose.
In real life situation SQLs are usually part of modules and codes. So using hint at SQL level, its not possible to implement the hint in code. Also, using hints in SQL at code level is not a good practice for obvious reason.

If we create a baseline based on hints than baseline will be automatically used for that SQL and plan will be same as that using hint.

Procedure is simple, just use the hint and run sql couple of times. One you do that, the plan will be automatically stored in shared_pool.

Find a SQL ID

SQL> select sql_id from v$sql where sql_text like 'select /*+ index(T_IDX T) +*/ * from%';

SQL_ID
-------------
50tsvs88gx382

Use DBMS_SPM.load_plans_from_cursor_cache to create baseline from cursor cache.

DECLARE
  l_plans_loaded  PLS_INTEGER;
BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
    sql_id => '50tsvs88gx382');
END;
/

At this point we have 2 SQLs –

50tsvs88gx382 – using index hint
5pvxxjg6n7mrp – Original query without using hint. This is doing FTS

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

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES NO  NO
SYS_SQL_3f0d350a1c24a8d4       SYS_SQL_PLAN_1c24a8d4ae82cf72  YES YES NO

SQL>

SQL_HANDLE – SYS_SQL_1447ba3a1d83920f is for original query without hint. This was having only 1 baseline, but when we loaded the baseline for query 50tsvs88gx382 which is using hint, it automatically created the baseline for original query. 11g is smart enought to filter the hints and create a baseline for original query.

At this stage you have 2 option

1) Accept plan SYS_SQL_PLAN_1d83920fae82cf72 for SQL_HANDLE SYS_SQL_1447ba3a1d83920f
2) Link sql plan SYS_SQL_PLAN_1c24a8d4ae82cf72 of SQL_HANDLE SYS_SQL_3f0d350a1c24a8d4 to SQL_HANDLE SYS_SQL_1447ba3a1d83920f

You already know option 1.

Lets see option 2.

To link the plan of some other SQL_HANDLE (SYS_SQL_3f0d350a1c24a8d4) to our SQL_HANDLE (SYS_SQL_1447ba3a1d83920f), you can use same procedure dbms_spm.load_plans_from_cursor_cache

DECLARE
  l_plans_loaded  PLS_INTEGER;
BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
    sql_id => '50tsvs88gx382',
    PLAN_HASH_VALUE => 470836197,
    SQL_HANDLE => 'SYS_SQL_1447ba3a1d83920f');
END;
/

PL/SQL procedure successfully completed.

In above procedure,
– SQL_ID is the new SQL_ID having hints.
– PLAN_HASH_VALUE is the plan hash value of SQL with hints. This is the plan we want to assign to our SQL_HANDLE
– SQL_HANDLE is the name of our SQL Handle to which we want to assign the plan

After assigning the plan, you will see the status as below.

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

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES YES NO
SYS_SQL_3f0d350a1c24a8d4       SYS_SQL_PLAN_1c24a8d4ae82cf72  YES YES NO

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |  1048K|    11M|   465   (2)| 00:00:06 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  1048K|    11M|   465   (2)| 00:00:06 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  1048K|       |   209   (2)| 00:00:03 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

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

18 rows selected.

SQL>

Hope this helps !!

Advertisement

Oracle SQL Plan Management – Part 2

SQL Plan Management – Oracle Database 11g

In SPM part 1 we saw information about SPM and how to automatically capture the baseline.
In this part we will see how to manually capture the baseline and the affect of using FIXED variable.

Capturing Baseline Manually

Occasionally we have to to capture the baseline manually. This could happen while tuning the query in our database where we have optimizer_capture_sql_plan_baselines parameter set to FALSE.

We have 2 procedures available for capturing the baselines manually.

  1. LOAD_PLANS_FROM_SQLSET

  2. LOAD_PLANS_FROM_CURSOR_CACHE

Above procedures are part of DBMS_SPM packages.

LOAD_PLANS_FROM_SQLSET procedure is used mainly while doing the upgrade from Oracle database 10g to Oracle database 11g.

The procedure is to

  1. create a SQL Tuning set in 10g and pack all the SQLs in the library cache along with there plans into SQL tuning set  – Procedure DBMS_SQLTUNE.CREATE_SQLSET and DBMS_SQLTUNE.LOAD_SQLSET
  2. Then to create a staging table and load this SQL tuning set into the staging table – Procedure DBMS_SQLTUNE.CREATE_STGTAB_SQLSET and DBMS_SQLTUNE.PACK_STGTAB_SQLSET
  3. Update database to 11g
  4. Unpack the staging table into SQL Tuning set you created before – Procedure DBMS_SQLTUNE.UNPACK_STGTAB_SQLSET
  5. Create SQL Baselines from the SQL tuning set – Procedure DBMS_SPM.LOAD_PLANS_FROM_SQLSET

LOAD_PLANS_FROM_CURSOR_CACHE is used after 11g upgrade when we have optimizer_capture_sql_plan_baselines set to false and we want to create baseline for a query in order to stablize the plan.



SQL> select col1, count(1) from T group by col1;

      COL1   COUNT(1)
---------- ----------
         1          1
         2          3
         3      98304

SQL> show parameters capture

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_capture_sql_plan_baselines boolean     FALSE

SQL>

Currently we dont have any baselines in the database.


SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

no rows selected

SQL>

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

SQL> set line 999
SQL> set pagesize 999
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

13 rows selected.

SQL>

Lets create a baseline manually for this statement. For creating baselines manually we need to have the statement in cache first.
Lets run the statement once and find the SQL ID.


SQL> select sql_id, sql_text from v$sql where sql_text like 'select * from T where%';

SQL_ID        SQL_TEXT
------------- ------------------------------------------------------------------------------------------
5pvxxjg6n7mrp select * from T where col1 = 1

Now we will use LOAD_PLANS_FROM_CURSOR_CACHE to create a baseline

SQL> DECLARE
  2  l_plans_loaded  PLS_INTEGER;
  3  BEGIN
  4  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
  5  sql_id => '5pvxxjg6n7mrp');
  6  END;
  7  /

PL/SQL procedure successfully completed.

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                         ENA ACC FIX
------------------------------ ------------------------------ -------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1   YES YES NO

SQL>

So we can see that SQL Plan baseline is created as well as accepted.
We will check out the plan from baseline now, but it should be full table scan.


SQL> SET LONG 10000
SQL> select * from TABLE(DBMS_XPLAN.display_sql_plan_baseline(plan_name=>'SYS_SQL_PLAN_1d83920f94ecae5c'));

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

--------------------------------------------------------------------------------
SQL handle: SYS_SQL_1447ba3a1d83920f
SQL text: select * from T where col1 = 1
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_1d83920f94ecae5c
Enabled: YES     Fixed: NO      Accepted: YES     Origin: MANUAL-LOAD
--------------------------------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

24 rows selected.

SQL>

Now lets create an index on the table on COL1.

SQL> create index T_IDX on T(COL1);

Index created.

SQL>

Now since the baseline is used, same query will go for a full table scan even after creating an index.


SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

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

17 rows selected.

SQL>

So as you can see we have a baseline SYS_SQL_PLAN_1d83920f94ecae5c getting used for this query.

Lets go ahead and try creating another baseline for this SQL. May be this time we will get better plan.

SQL ID remains the same.


SQL> DECLARE
  2  l_plans_loaded  PLS_INTEGER;
  3  BEGIN
  4  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
  5  sql_id => '5pvxxjg6n7mrp');
  6  END;
  7  /

PL/SQL procedure successfully completed.

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                         ENA ACC FIX
------------------------------ ------------------------------ -------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1   YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1   YES NO  NO

And yes, we can see Oracle has created another baseline here. But ACCEPTED is still NO for the new plan.
This is because we haven’t evaluated the plan yet. If we would have set optimizer_capture_sql_plan_baselines parameter to true, it would have automatically captured and made ACCEPTED=YES after doing automatic evaluation.
Since this is false, we have to evaluate whether new plan is better or not. This process of evaluating new baseline is called evolving.

Evolving a plan actually compares the cost and several other factors like DISK_READS, BUFFER_GETS, ELAPSED_TIME etc for all the plans that are available in DBA_SQL_PLAN_BASELINES table for that particular SQL HANDLE.
So in our case when we evolve the plan it will compare plan SYS_SQL_PLAN_1d83920f94ecae5c and SYS_SQL_PLAN_1d83920fae82cf72

Just to be certain we will display the plan SYS_SQL_PLAN_1d83920fae82cf72



SQL> select * from TABLE(DBMS_XPLAN.display_sql_plan_baseline(plan_name=>'SYS_SQL_PLAN_1d83920fae82cf72'));

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

--------------------------------------------------------------------------------
SQL handle: SYS_SQL_1447ba3a1d83920f
SQL text: select * from T where col1 = 1
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_1d83920fae82cf72
Enabled: YES     Fixed: NO      Accepted: NO      Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       | 49154 |  2976K|     6   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     | 49154 |  2976K|     6   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX | 49154 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

25 rows selected.

SQL>

So it is indeed using the index on the table.

Here there are 2 things again.

  1. Evolving the plan automatically – Using procedure DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE
  2. Manually accepting the plan – Using procedure ALTER_SQL_PLAN_BASELINE setting ACCEPTED=>YES

In the first method optimizer will evaluate based on all the factors (we will see those factors below) and “selects” the best plan. I have highlighted select here because optimizer is going to select from the set of existing plans, its not going to parse and create a whole other plan.
So its upto the optimizer to decide which plan is best among the available plan.

In method 2, we can decide and select which ever plan we think is correct. This based on our knowledge of the type of SQL we are running and what resources we have with us.

Can we have 2 plans in ACCEPTED status for a query?

Yes we can.

Which plan optimizer will choose out of the two?

Always the best among the two. If more than 1 plan is ACCEPTED than optimizer will choose the best plan to be used for the query.
Example if we use a query “select * from T where col = :B1”
Here we have used a bind variable. Lets say we have 2 plans with us right now
Plan 1) Full table scan
Plan 2) Index Range scan

Which plan optimizer will choose depends upon the value of bind variable.
Yes, in 11g we have something called “Adaptive Cursor Sharing”, but this is little off the topic. We will cover “Adaptive Cursor Sharing” in different article.

So if we supply value as 1 or 2 and since there is only 1 row with col1=1 and 3 rows with col1=2, optimizer will use Plan 2) Index Range Scan.
If we supply value as 3, optimizer will use Plan 1) Full table scan.

So imagine a case where we have 5-6 plans based on selectivity of different tables involved in a complex query, in that case its a good idea to keep all those plans as ACCEPTED. Optimizer will always choose the best among those based on selectivity of the data.

We have seen evolving the plan in Part 1 – https://avdeo.com/2011/06/02/oracle-sql-plan-management-part-1/
Lets set ACCEPTED=TRUE manually now.


SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2    l_plans_altered  PLS_INTEGER;
  3  BEGIN
  4    l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
  5      sql_handle      => 'SYS_SQL_1447ba3a1d83920f',
  6      plan_name       => 'SYS_SQL_PLAN_1d83920fae82cf72',
  7      attribute_name  => 'ACCEPTED',
  8      attribute_value => 'YES');
  9
 10    DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
 11  END;
 12  /
Plans Altered: 1

PL/SQL procedure successfully completed.

SQL>

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                         ENA ACC FIX
------------------------------ ------------------------------ -------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1   YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1   YES YES NO

SQL>

Lets check the explain plan for value 1.


SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       | 49154 |  2976K|    56   (2)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     | 49154 |  2976K|    56   (2)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX | 49154 |       |    10   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

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

18 rows selected.

SQL>

So its using SYS_SQL_PLAN_1d83920fae82cf72 baselines.

What is the affect of using FIXED=YES ?

Fixed attribute will fix the plan for a query. So even if we have a better plan, the query will still not be using it. It is like disabling (ENABLED=NO, ACCEPTED=NO) all the plans for that SQL Handle.

Let make FIXED=YES for the first baseline (SYS_SQL_PLAN_1d83920f94ecae5c) which does full table scan.
To change this attribute we can again use DBMS_SPM.ALTER_SQL_PLAN_BASELINE procedure



SQL> set serveroutput on
SQL> DECLARE
  2    l_plans_altered  PLS_INTEGER;
  3  BEGIN
  4    l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
  5      sql_handle      => 'SYS_SQL_1447ba3a1d83920f',
  6      plan_name       => 'SYS_SQL_PLAN_1d83920f94ecae5c',
  7      attribute_name  => 'FIXED',
  8      attribute_value => 'YES');
  9
 10    DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
 11  END;
 12  /
Plans Altered: 1

PL/SQL procedure successfully completed.

SQL>

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                                           ENA ACC FIX
------------------------------ ------------------------------ -------------------------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1                     YES YES YES
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1                     YES YES NO

SQL>

Lets check out the explain plan for “select * from T where col1 = 1” query


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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

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

17 rows selected.

SQL>

So if you see now, same query which was earlier using index scan has now shifted to full table scan. Thats the magic of SPM !!
I would recommend not using FIXED attribute at all. You are just killing the functionality provided by 11g.

However, if we make both plans as FIXED then ??


set serveroutput on                                           
DECLARE                                                       
  l_plans_altered  PLS_INTEGER;                               
BEGIN                                                         
  l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(        
    sql_handle      => 'SYS_SQL_1447ba3a1d83920f',            
    plan_name       => 'SYS_SQL_PLAN_1d83920fae82cf72',       
    attribute_name  => 'FIXED',                               
    attribute_value => 'YES');                                
                                                              
  DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered); 
END;                                                          
/                                                             

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                                           ENA ACC FIX
------------------------------ ------------------------------ -------------------------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1                     YES YES YES
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1                     YES YES YES

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       | 49154 |  2976K|     6   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     | 49154 |  2976K|     6   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX | 49154 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

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

18 rows selected.

SQL>

It will again go back to best among the fixed plans.

In the next part, I will cover how to transfer the baseline from one database to another and also how to set a specific plan for a query using hint and then creating baseline for that.

Hope this helps !!