No need to change source code, hint it using a baseline
October 13, 2011 6 Comments
Just a quick end-to-end walkthrough on how to transfer a plan from one sql statement to another via SQL Plan Baselines.
This has the potential to be very useful when it comes to tuning a production sql statement without actually touching the code and is much more straightforward than hacking outlines or profiles. Plus it’s supported – win/win.
Completely artificial test case – we’re going to force a “select * from t1” to change from a full table scan to a full scan of an index.
Setup:
SQL>create table t1 2 (col1 number 3 ,constraint pk_t1 primary key (col1)); Table created.
The plan we want to force:
SQL>select /*+ index(t1 pk_t1) */ 2 * 3 from t1; no rows selected SQL>select * from table(dbms_xplan.display_cursor()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID an6t9h9g5s3vh, child number 0 ------------------------------------- select /*+ index(t1 pk_t1) */ * from t1 Plan hash value: 646159151 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | INDEX FULL SCAN | PK_T1 | 1 | 13 | 2 (0)| 00:00:01 | -------------------------------------------------------------------------- 17 rows selected.
The statement whose plan we want to change:
SQL>select * from t1; no rows selected SQL>select * from table(dbms_xplan.display_cursor()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID 27uhu2q2xuu7r, child number 0 ------------------------------------- select * from t1 Plan hash value: 3617692013 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | TABLE ACCESS FULL| T1 | 1 | 13 | 2 (0)| 00:00:01 | -------------------------------------------------------------------------- 17 rows selected.
Let’s change it using DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE:
SQL>declare 2 sqltext clob; 3 spm_op pls_integer; 4 begin 5 sqltext := 'select * from t1'; 6 spm_op := 7 dbms_spm.load_plans_from_cursor_cache 8 (sql_id => 'an6t9h9g5s3vh', 9 plan_hash_value => 646159151, 10 sql_text => sqltext); 12 end; 13 / PL/SQL procedure successfully completed.
And let’s check it’s changed:
SQL>select * from t1; no rows selected SQL> SQL>select * from table(dbms_xplan.display_cursor()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID 27uhu2q2xuu7r, child number 1 An uncaught error happened in prepare_sql_statement : ORA-01403: no data found NOTE: cannot fetch plan for SQL_ID: 27uhu2q2xuu7r, CHILD_NUMBER: 1 Please verify value of SQL_ID and CHILD_NUMBER; It could also be that the plan is no longer in cursor cache (check v$sql_plan) 8 rows selected. SQL>select * from t1; no rows selected SQL>select * from table(dbms_xplan.display_cursor()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID 27uhu2q2xuu7r, child number 1 ------------------------------------- select * from t1 Plan hash value: 646159151 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 26 (100)| | | 1 | INDEX FULL SCAN | PK_T1 | 82 | 1066 | 26 (0)| 00:00:01 | -------------------------------------------------------------------------- Note ----- - SQL plan baseline SQL_PLAN_6x6k5dhdcczn6690169bf used for this statement 17 rows selected. SQL>
Easy. Job done.
Note that you need the SQL_ID and PLAN_HASH_VALUE of the SOURCE statement (i.e. the plan you want to transfer).
This plan has to be in the cache – which shouldn’t be a problem because the main scenario we’re talking about here is doing some adhoc tuning on a statement, maybe by adding some hints to get a particular access pathe, with the aim of propagating the tuned plan to the production statement.
For the TARGET statement – i.e. the problem statement that you want to transfer the plan to – you just need the sql text.
If it’s a long sql statement it might be easier to this directly from v$sql.sql_fulltext and pass it in.
Why do you only need the sql text? Because it gets converted to a signature as per DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE. And signature is just a different sort of hash after the statement has been adjusted for white space and case insensitivity.
But you could use the overloaded version of LOAD_PLANS_FROM_CURSOR_CACHE and pass in SQL_HANDLE instead if this statement is already baselined. (Anyone know how SQL_HANDLE is generated? Yet another hash?).
Curious that this work.
When you use SQL_TEXT, you will have to be careful to specify the *exact* SQL Text (warts and all) as is called by the actual application.
Reading the documentation, I guess that you’ve used the third overload.It’s still difficult to understand.
Hi Hemant,
I did post a comment but I’ve now expanded it to a new post.
Does that cover what you meant?
Any other concerns?
Cheers,
Dominic
Pingback: No need to change source code, hint it using a baseline II « OraStory
Yes, the follow up post is Cool ! Thanks.
As for SQL_HANDLE : http://oracleprof.blogspot.com/2011/07/how-to-find-sqlid-and-planhashvalue-in.html
“SQL_HANDLE contain hexadecimal representation of EXACT_MATCHING_SIGNATURE from V$SQL”
Good.
Thanks for the link. I’ve read that article before but clearly that bit about sql handle didn’t stick.
Thanks.
Pingback: SQL Patch I « OraStory