Stored outline is still the only option available on standard edition databases for achieving plan stability. On 11gR2 there's dbms_spm.MIGRATE_STORED_OUTLINE to migrate stored outlines to plan baselines but that doesn't work on standard edition.
This post list steps to change the execution plan of a SQL query using stored outlines. The environment is Standard Edition 11gR1 (11.1.0.7.13) running on RHEL 5 (64bit). The cursor_sharing is set to similar. How cursor_sharing affect stored outline is available on 132547.1
1. Grant the user creating stored outlines following privileges
8. Once the use of stored outline is enable it's time to check if they get used when SQL get executed. user_outlines has a column called used which will say used or noused but in this case display_cursor will be used to see if an outline is used with the SQL.
9. Create two private outlines from the two outlines created above
11. Update public outline with the private outline and turn-off the use of private outlines
However using stored outlines on JDBC application has an additional challenge. This has to do with how literals and replace with bind variables when using SQL*plus and JDBC. Running the original SQL text through JDBC application doesn't result in the stored outline being used.
In order to outline to be used create a new outline for JDBC SQL text and associate all hints in the existing outline with it.
Useful metalink notes
Using Stored Outlines [ID 132547.1]
Stored Outline Not Working [ID 802044.1]
Stored Outline Quick Reference [ID 67536.1]
Session Parameter: USE_STORED_OUTLINES [ID 68642.1]
How To Match a SQL Statement to a Stored Outline [ID 743312.1]
Editing Stored Outlines in Oracle10g and Oracle11g [ID 726802.1]
How to Enable USE_STORED_OUTLINES Permanently [ID 560331.1]
How To retreive the Outline Data from a stored execution plan [ID 736529.1]
HOW TO: Create a Stored Outline Based Upon an Existing Cursor [ID 445126.1]
How to generate an outline with a good plan loaded into shared_pool [ID 463288.1]
How to Edit a Stored Outline to Use the Plan from Another Stored Outline [ID 730062.1]
How to Transfer Stored Outlines from One Database to Another (9i and above) [ID 728647.1]
This post list steps to change the execution plan of a SQL query using stored outlines. The environment is Standard Edition 11gR1 (11.1.0.7.13) running on RHEL 5 (64bit). The cursor_sharing is set to similar. How cursor_sharing affect stored outline is available on 132547.1
1. Grant the user creating stored outlines following privileges
grant create any outline to asanga;2. Following SQL is considered the original SQL of which execution plan will be changed
grant alter any outline to asanga;
SQL> select count(*) from big_table where p_id=15151;Currently the above SQL's plan uses an index and this will be changed to use a full table scan (no consideration is given to the suitability of this change, it's merely used to illustrate how to change the execution plan using stored outlines). Generate the desired execution plan, in this case this is done using hints
SQL> select /*+ full(big_table) */ count(*) from big_table where p_id=151;This generates two sql id as the sql text is different
SQL> select sql_id,hash_value,child_number,sql_text from v$sql where sql_text like '%big_table%';4. Stored outlines will be created using these two plans. DBMS_OUTLN.CREATE_OUTLINE will be used as it allows to create stored outlines from existing cursors (445126.1)
SQL_ID HASH_VALUE CHILD_NUMBER SQL_TEXT
------------- ---------- ------------ ---------------------------------------------------------------------------
80151fvm1d9af 3860243790 0 select count(*) from big_table where p_id=:"SYS_B_0"
81920u5c0ufyb 1477262283 0 select /*+ full(big_table) */ count(*) from big_table where p_id=:"SYS_B_0"
SQL> alter session set create_stored_outlines = TRUE;6. This would have generated two stored outlines. Which could be viewed using user_otulines view
SQL> exec dbms_outln.CREATE_OUTLINE(3860243790,0);
PL/SQL procedure successfully completed.
SQL> exec dbms_outln.CREATE_OUTLINE(1477262283,0);
PL/SQL procedure successfully completed.
SQL> alter session set create_stored_outlines = false;
SQL> select name,sql_text from user_outlines;5. Give easily identifiable names to stored outline so it is easier to identify which outline is associated with which plan. In this case SYS_OUTLINE_12121716093232802 is associated with plan using the index and SYS_OUTLINE_12121716094181604 is associated with plan using full table scan.
NAME SQL_TEXT
------------------------------ ---------------------------------------------------------------------------
SYS_OUTLINE_12121716093232802 select count(*) from big_table where p_id=:"SYS_B_0"
SYS_OUTLINE_12121716094181604 select /*+ full(big_table) */ count(*) from big_table where p_id=:"SYS_B_0"
SQL> alter outline SYS_OUTLINE_12121716093232802 rename to with_index;7. Enable use of stored outlines on the system.
Outline altered.
SQL> alter outline SYS_OUTLINE_12121716094181604 rename to full_table;
Outline altered.
SQL> select name,sql_text from user_outlines;
NAME SQL_TEXT
------------------------------ ---------------------------------------------------------------------------
WITH_INDEX select count(*) from big_table where p_id=:"SYS_B_0"
FULL_TABLE select /*+ full(big_table) */ count(*) from big_table where p_id=:"SYS_B_0"
SQL> alter system set use_stored_outlines=trueThis could be done for session as well. Stored outlines are not enable by default. To enable stored outlines during system start up add a on database trigger and include the statement above inside the trigger.
create or replace trigger enable_outlines_trigAbove trigger will add entries to alert.log. Refer 560331.1 for more information.
after startup on database
begin
sys.dbms_system.ksdwrt(2, 'AP-ORA-01 going to enable outline');
execute immediate('alter system set use_stored_outlines=true');
sys.dbms_system.ksdwrt(2, 'AP-ORA-01 outline enabled');
end;
/
8. Once the use of stored outline is enable it's time to check if they get used when SQL get executed. user_outlines has a column called used which will say used or noused but in this case display_cursor will be used to see if an outline is used with the SQL.
SQL> select count(*) from big_table where p_id=1;This shows that outline associated with index plan is being used when the original sql is run.
SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS OUTLINE'));
SQL_ID 80151fvm1d9af, child number 0
-------------------------------------
select count(*) from big_table where p_id=:"SYS_B_0"
Plan hash value: 1795900200
-----------------------------------------------
| Id | Operation | Name | E-Rows |
-----------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | SORT AGGREGATE | | 1 |
|* 2 | INDEX RANGE SCAN| P_ID_IDX | 2 |
-----------------------------------------------
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.7')
DB_VERSION('11.1.0.7')
OPT_PARAM('_optimizer_extended_cursor_sharing' 'none')
OPT_PARAM('_optimizer_extended_cursor_sharing_rel' 'none')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1000)
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "BIG_TABLE"@"SEL$1" ("BIG_TABLE"."P_ID"))
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("P_ID"=:SYS_B_0)
Note
-----
- outline "WITH_INDEX" used for this statement
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
SQL> select /*+ full(big_table) */ count(*) from big_table where p_id=2;This shows that when the SQL run with full table hint the full_table outline get used. Next step is to make the original SQL execute with plan generated for SQL with the hint.
SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS OUTLINE'));
SQL_ID 81920u5c0ufyb, child number 0
-------------------------------------
select /*+ full(big_table) */ count(*) from big_table where
p_id=:"SYS_B_0"
Plan hash value: 599409829
-------------------------------------------------
| Id | Operation | Name | E-Rows |
-------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | SORT AGGREGATE | | 1 |
|* 2 | TABLE ACCESS FULL| BIG_TABLE | 2 |
-------------------------------------------------
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.7')
DB_VERSION('11.1.0.7')
OPT_PARAM('_optimizer_extended_cursor_sharing' 'none')
OPT_PARAM('_optimizer_extended_cursor_sharing_rel' 'none')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1000)
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "BIG_TABLE"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("P_ID"=:SYS_B_0)
Note
-----
- outline "FULL_TABLE" used for this statement
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
9. Create two private outlines from the two outlines created above
SQL> create private outline myindex from with_index;These private outlines will be modified such that MYFULLTABLE outline will be associated with the MYINDEX outline's hints. Set the hint count of MYINDEX to match the hint count of MYFULLTABLE
Outline created.
SQL> create private outline myfulltable from full_table;
Outline created.
SQL> select ol_name from ol$;
MYINDEX
MYFULLTABLE
SQL> update ol$ set hintcount=(select hintcount from ol$ where ol_name='MYFULLTABLE') where ol_name='MYINDEX';Delete the MYFULLTABLE private outline record
1 row updated.
SQL> delete from ol$ where ol_name='MYFULLTABLE';Rename the MYINDEX outline as MYFULLTABLE
1 row deleted.
SQL> update ol$ set ol_name='MYFULLTABLE' where ol_name='MYINDEX';At the end of this there will be only one private outline and it will be associated with the original SQL text
1 row updated.
SQL> select ol_name,sql_text from ol$;Execute a commit as ol$* tables are temporary tables and refresh the private outline.
MYFULLTABLE select count(*) from big_table where p_id=:"SYS_B_0"
SQL> commit;10. Before the private outline is made permanent it would be tested by making session to use private outlines instead of public outlines.
Commit complete.
SQL> execute dbms_outln_edit.refresh_private_outline('MYFULLTABLE');
PL/SQL procedure successfully completed.
SQL> alter session set use_private_outlines=true;From the output it could be seen that private outline is being used and execution plan has changed to full table scan and the SQL text is the original SQL text.
Session altered.
SQL> select count(*) from big_table where p_id=1;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS OUTLINE'));
SQL_ID 02971r98szzu3, child number 0
-------------------------------------
select count(*) from big_table where p_id=:"SYS_B_0"
Plan hash value: 599409829
-------------------------------------------------
| Id | Operation | Name | E-Rows |
-------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | SORT AGGREGATE | | 1 |
|* 2 | TABLE ACCESS FULL| BIG_TABLE | 2 |
-------------------------------------------------
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.7')
DB_VERSION('11.1.0.7')
OPT_PARAM('_optimizer_extended_cursor_sharing' 'none')
OPT_PARAM('_optimizer_extended_cursor_sharing_rel' 'none')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1000)
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "BIG_TABLE"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("P_ID"=:SYS_B_0)
Note
-----
- outline "MYFULLTABLE" used for this statement
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
11. Update public outline with the private outline and turn-off the use of private outlines
SQL> create or replace outline full_table from private MYFULLTABLE;12. At this stage there's only one public outline available (instead of two started with) and that will be associated with the full table scan plan.
Outline created.
SQL> alter session set use_private_outlines=false;
SQL> select name,sql_text from user_outlines;13. Execute the original SQL text and confirm the modified outline is used
NAME SQL_TEXT
------------ ----------------------------------------------------
FULL_TABLE select count(*) from big_table where p_id=:"SYS_B_0"
SQL> SELECT node, stage, join_pos, hint FROM user_outline_hints WHERE name ='FULL_TABLE';
1 1 0 OUTLINE_LEAF(@"SEL$1")
1 1 0 FIRST_ROWS(1000)
1 1 0 OPT_PARAM('optimizer_index_caching' 90)
1 1 0 OPT_PARAM('_optimizer_extended_cursor_sharing_rel' 'none')
1 1 0 DB_VERSION('11.1.0.7')
1 1 0 IGNORE_OPTIM_EMBEDDED_HINTS
1 1 0 OPTIMIZER_FEATURES_ENABLE('11.1.0.7')
1 1 0 OPT_PARAM('_optimizer_extended_cursor_sharing' 'none')
1 1 0 OPT_PARAM('optimizer_index_cost_adj' 25)
1 1 1 FULL(@"SEL$1" "BIG_TABLE"@"SEL$1")
SQL>select count(*) from big_table where p_id=151;This concludes how to change the execution plan using stored outlines.
SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS OUTLINE'));
SQL_ID 80151fvm1d9af, child number 0
-------------------------------------
select count(*) from big_table where p_id=:"SYS_B_0"
Plan hash value: 599409829
-------------------------------------------------
| Id | Operation | Name | E-Rows |
-------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | SORT AGGREGATE | | 1 |
|* 2 | TABLE ACCESS FULL| BIG_TABLE | 2 |
-------------------------------------------------
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.7')
DB_VERSION('11.1.0.7')
OPT_PARAM('_optimizer_extended_cursor_sharing' 'none')
OPT_PARAM('_optimizer_extended_cursor_sharing_rel' 'none')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1000)
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "BIG_TABLE"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("P_ID"=:SYS_B_0)
Note
-----
- outline "FULL_TABLE" used for this statement
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
However using stored outlines on JDBC application has an additional challenge. This has to do with how literals and replace with bind variables when using SQL*plus and JDBC. Running the original SQL text through JDBC application doesn't result in the stored outline being used.
SQL> select sql_id from v$sql where sql_text like '%big_table%';There's no mention of stored outline being used in the note section and plan is using a index range scan instead of full table scan. More over a new sql id is also generated.
4378mr12fsdrp
SQL> select * from table(dbms_xplan.display_cursor('4378mr12fsdrp',0,'ALLSTATS OUTLINE'));
SQL_ID 4378mr12fsdrp, child number 0
-------------------------------------
select count(*) from big_table where p_id=:1
Plan hash value: 1795900200
-----------------------------------------------
| Id | Operation | Name | E-Rows |
-----------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | SORT AGGREGATE | | 1 |
|* 2 | INDEX RANGE SCAN| P_ID_IDX | 2 |
-----------------------------------------------
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.7')
DB_VERSION('11.1.0.7')
OPT_PARAM('_optimizer_extended_cursor_sharing' 'none')
OPT_PARAM('_optimizer_extended_cursor_sharing_rel' 'none')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1000)
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "BIG_TABLE"@"SEL$1" ("BIG_TABLE"."P_ID"))
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("P_ID"=:1)
Note
-----
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
SQL> select sql_id,sql_text from v$sql where sql_text like '%big_table%';Comparing the two sql text is could be seen that only difference is the text used for bind variables. In JDBC :1,:2,:3 and etc is used for bind variables while on SQL plus with cursor_sharing set to similar or force a text is used SYS_B_0,SYS_B_1 and etc. This makes the two SQL non-identical thus resulting in a new SQL id and outline not being used.
SQL_ID SQL_TEXT
------------- ----------------------------------------------------
4378mr12fsdrp select count(*) from big_table where p_id=:1
80151fvm1d9af select count(*) from big_table where p_id=:"SYS_B_0"
In order to outline to be used create a new outline for JDBC SQL text and associate all hints in the existing outline with it.
SQL> alter session set create_stored_outlines = TRUE;After the stored outline change executing the original SQL text through JDBC shows the newly created outline being used and execution plan using full table scan.
Session altered.
SQL> exec dbms_outln.CREATE_OUTLINE(1156331253,0);
PL/SQL procedure successfully completed.
SQL> alter session set create_stored_outlines = false;
Session altered.
SQL> select name,sql_text from user_outlines;
NAME SQL_TEXT
------------------------------ ------------------------------------------------------
SYS_OUTLINE_12121716491461306 select count(*) from big_table where p_id=:1
FULL_TABLE select count(*) from big_table where p_id=:"SYS_B_0"
SQL> alter outline SYS_OUTLINE_12121716491461306 rename to jdbc_outline;
Outline altered.
SQL> create or replace private outline myfulltable from full_table;
Outline created.
SQL> create or replace private outline myjdbc from jdbc_outline;
Outline created.
SQL> select ol_name from ol$;
OL_NAME
------------------------------
MYFULLTABLE
MYJDBC
SQL> SELECT ol_name,sql_text from ol$;
OL_NAME SQL_TEXT
------------------------------ ------------------------------------------------------
MYFULLTABLE select count(*) from big_table where p_id=:"SYS_B_0"
MYJDBC select count(*) from big_table where p_id=:1
SQL> update ol$ set hintcount=(select hintcount from ol$ where ol_name='MYFULLTABLE') where ol_name='MYJDBC';
1 row updated.
SQL> delete from ol$ where ol_name='MYFULLTABLE';
1 row deleted.
SQL> update ol$ set ol_name='MYFULLTABLE' where ol_name='MYJDBC';
1 row updated.
SQL> commit;
SQL> SELECT ol_name,sql_text from ol$;
OL_NAME SQL_TEXT
------------------------------ ----------------------------------------------
MYFULLTABLE select count(*) from big_table where p_id=:1
SQL> execute dbms_outln_edit.refresh_private_outline('MYFULLTABLE');
PL/SQL procedure successfully completed.
SQL> create or replace outline jdbc_outline from private MYFULLTABLE;
Outline created.
SQL> alter session set use_private_outlines=false;
Session altered.
SQL> select name,sql_text from user_outlines;
NAME SQL_TEXT
------------------------------ -----------------------------------------------------
JDBC_OUTLINE select count(*) from big_table where p_id=:1
FULL_TABLE select count(*) from big_table where p_id=:"SYS_B_0"
SQL> select * from table(dbms_xplan.display_cursor('4378mr12fsdrp',0,'ALLSTATS OUTLINE'));
SQL_ID 4378mr12fsdrp, child number 0
-------------------------------------
select count(*) from big_table where p_id=:1
Plan hash value: 599409829
-------------------------------------------------
| Id | Operation | Name | E-Rows |
-------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | SORT AGGREGATE | | 1 |
|* 2 | TABLE ACCESS FULL| BIG_TABLE | 2 |
-------------------------------------------------
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.7')
DB_VERSION('11.1.0.7')
OPT_PARAM('_optimizer_extended_cursor_sharing' 'none')
OPT_PARAM('_optimizer_extended_cursor_sharing_rel' 'none')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1000)
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "BIG_TABLE"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("P_ID"=:1)
Note
-----
- outline "JDBC_OUTLINE" used for this statement
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
Useful metalink notes
Using Stored Outlines [ID 132547.1]
Stored Outline Not Working [ID 802044.1]
Stored Outline Quick Reference [ID 67536.1]
Session Parameter: USE_STORED_OUTLINES [ID 68642.1]
How To Match a SQL Statement to a Stored Outline [ID 743312.1]
Editing Stored Outlines in Oracle10g and Oracle11g [ID 726802.1]
How to Enable USE_STORED_OUTLINES Permanently [ID 560331.1]
How To retreive the Outline Data from a stored execution plan [ID 736529.1]
HOW TO: Create a Stored Outline Based Upon an Existing Cursor [ID 445126.1]
How to generate an outline with a good plan loaded into shared_pool [ID 463288.1]
How to Edit a Stored Outline to Use the Plan from Another Stored Outline [ID 730062.1]
How to Transfer Stored Outlines from One Database to Another (9i and above) [ID 728647.1]