I tuned a production query this week using outline hints and a SQL profile. This is like other examples I have posted but may have enough differences to be worth documenting here.
A query in a batch job was taking over an hour to run. I got it to run in under 30 minutes.
Old plan 898524 rows selected. Elapsed: 01:13:34.35 New plan 899018 rows selected. Elapsed: 00:28:00.28
The new plan ran about 45 minutes faster.
The main point here is that I was able to improve the plan in a more selective way using an outline hint than I could have with regular hints.
I ran the problem query using my test2.sql script to show the expected and actual number or rows in each step in the plan and to show the amount of time spent on each step. The output looked like this:
This is the typical example of a bad plan using an index in a case where a full scan is more efficient. Most of the time is spent in step 4 doing 898 thousand table lookups. So, to fix this query I tried a FULL hint to get rid of the SKIP SCAN, but it did not help. I tried various combinations of FULL hints on the four tables in the plan and they all resulted in inefficient plans with different types of joins than the original plan.
Full hints: SELECT /*+ FULL(DD) FULL(ORG) FULL(WINDOW1) FULL(WINDOW2) */ DD.ROW_ID ... Plan: ----------------------------------------------- | Id | Operation | Name | ----------------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | HASH JOIN RIGHT OUTER | | |* 2 | TABLE ACCESS FULL | S_ORG_EXT_XM | |* 3 | HASH JOIN RIGHT OUTER| | |* 4 | TABLE ACCESS FULL | S_ORG_EXT_XM | |* 5 | HASH JOIN | | |* 6 | TABLE ACCESS FULL | S_ORG_EXT | |* 7 | TABLE ACCESS FULL | S_ORG_EXT_XM | -----------------------------------------------
Then I got the idea of using an outline hint. The plan.sql script that I use to run EXPLAIN PLAN on a query outputs an outline hint. In this case the outline hint looks like this:
The main idea that I wanted to write this post about is to take this outline hint and leave everything else the same but to replace the skip scan with a full table scan. Adding a FULL hint was changing a lot of things about the plan. But if I start with the outline hint, I can force the plan to remain the same except for the one thing I change. I ran plan.sql with the query with a full hint to get the outline with a full scan and then pulled out just the full scan line to put into the original plan’s outline. Here are the old and new lines that I used to edit to outline hints for the original query’s plan, an index skip scan and a full table scan.
Old: INDEX_SS(@"SEL$1" "DD"@"SEL$1" ("S_ORG_EXT_XM"."PAR_ROW_ID" "S_ORG_EXT_XM"."TYPE" "S_ORG_EXT_XM"."NAME" "S_ORG_EXT_XM"."CONFLICT_ID")) New: FULL(@"SEL$1" "DD"@"SEL$1")
Here is the edited version of the outline hint:
So, then I ran my test2.sql script but with the edited outline hint and the query ran in 28 minutes. So, all we had to do was put the outline hint into the existing SQL script.
But then I realized that I could finish off the performance improvement using a SQL Profile and not have to edit the script. The output for the run of my test2.sql script with the hint had this sql_id and plan_hash_value: 3m80wvb2v6vdq 42036438.
My slow production query had this sql id and plan hash value: dzfr3vz7z5p66 2238264355.
So, I passed these two sets of parameters to coe_xfr_sql_profile.sql in the sqlt/utl subdirectory creating these two scripts:
coe_xfr_sql_profile_3m80wvb2v6vdq_42036438.sql and coe_xfr_sql_profile_dzfr3vz7z5p66_2238264355.sql
So, I hacked the two scripts together to make coe_xfr_sql_profile_dzfr3vz7z5p66_42036438.sql which has the SQL text of the original query and the plan that my test script with the outline hint used. The scripts output by coe_xfr_sql_profile.sql save the text of the SQL statement and its plan when creating a new SQL Profile. You can get a little creative and connect production SQL text with a plan you have generated through hints.
9 DBMS_LOB.CREATETEMPORARY(sql_txt, TRUE); 10 DBMS_LOB.OPEN(sql_txt, DBMS_LOB.LOB_READWRITE); 11 -- SQL Text pieces below do not have to be of same length. 12 -- So if you edit SQL Text (i.e. removing temporary Hints), 13 -- there is no need to edit or re-align unmodified pieces. 14 wa(q'[SELECT DD.ROW_ID|| '||' || 15 DD.PAR_ROW_ID|| '||' || text of original query starts line 14 55 AND WINDOW2.X_DELIV]'); 56 wa(q'[ERY_WINDOW (+) = '2' 57 AND WINDOW2.TYPE (+)= 'DELIVERY TIMES']'); 58 DBMS_LOB.CLOSE(sql_txt); 59 h := SYS.SQLPROF_ATTR( 60 q'[BEGIN_OUTLINE_DATA]', 61 q'[IGNORE_OPTIM_EMBEDDED_HINTS]', text of query ends line 57. outline hint with full scan starts line 60 80 q'[USE_MERGE(@"SEL$1" "ORG"@"SEL$1")]', 81 q'[END_OUTLINE_DATA]'); 82 :signature := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt); 83 :signaturef := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt, TRUE); ends line 81
So, I ran the edited coe_xfr_sql_profile_dzfr3vz7z5p66_42036438.sql to put in the SQL Profile which related the production query to the plan generated by the edited outline hint and the production job ran in 28 minutes last night without any change to its SQL code. The outline hint allowed me to leave the original plan intact except for the one step that needed a full scan instead of an index skip scan. Then the SQL Profile trick allowed me to apply the better plan without any change to the application.
P.S. It looks like I didn’t have to hack together the sql profile script the way I did. See this post: https://www.bobbydurrettdba.com/2014/03/19/using-hints-with-coe_xfr_sql_profile-sql/
I could have just extracted the SQL Profile script like this:
coe_xfr_sql_profile.sql dzfr3vz7z5p66 42036438
That would have generated oe_xfr_sql_profile_dzfr3vz7z5p66_42036438.sql automatically combining the production SQL text with the plan with the outline hint and full scan instead of me editing it together manually.