Fixing SQL Plans: The hard way – Part 3

In part 1 of this series I showed you how to fix a SQL plan by picking good plan hints from other database and applying those hints/plan to your existing bad query

In part 2 of this series I showed you how to fix a SQL plan even if you don’t have another database having better plan. I explained you about hint you can use in the query and execute the query to get better plan. Then we can use the new plan generated and apply to the original SQL.

In this article we can go 1 step further. What if you don’t want to execute the SQL. May be because SQL gives too much of output or takes lot of time to complete.

We can generate the profile hints without even executing the SQL. This is possible using plan table

I am just explaining the steps which will be changed in this case

Once you identify the hint to be used, put the hint in the query and take explain plan

SQL>explain plan for
  2  select /*+ index(T T_IDX) */ count(1) from T where data_object_id > 65000 and status = 'VALID';



Plan hash value: 1789076273

| Id  | Operation             | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
|   0 | SELECT STATEMENT         |         |       1 |      12 |     953K  (1)| 00:38:35 |
|   1 |  SORT AGGREGATE          |         |       1 |      12 |          |         |
|*  2 |   TABLE ACCESS BY INDEX ROWID| T     | 16441 |     192K|     953K  (1)| 00:38:35 |
|*  3 |    INDEX RANGE SCAN         | T_IDX | 32883 |         |    8105   (3)| 00:00:20 |

Predicate Information (identified by operation id):

   2 - filter("STATUS"='VALID')
   3 - access("DATA_OBJECT_ID">65000)

16 rows selected.


Now if you check the plan table, you will have a column in plan_table called OTHER_XML

SQL>set long 9999
SQL>select other_xml from plan_table ;


<other_xml><info type="db_version"></info><info type="parse_schema"><![C
DATA["ORACLE_DBA"]]></info><info type="plan_hash">1789076273</info><info type="
"T"@"SEL$1" ("T"."DATA_OBJECT_ID"))]]></hint><hint><![CDATA[OUTLINE_LEAF(@"SEL$1
")]]></hint><hint><![CDATA[OPT_PARAM('optimizer_index_caching' 80)]]></hint><hin
t><![CDATA[OPT_PARAM('optimizer_index_cost_adj' 9999)]]></hint><hint><![CDATA[OP
T_PARAM('_optimizer_connect_by_cost_based' 'false')]]></hint><hint><![CDATA[OPT_
PARAM('_optim_peek_user_binds' 'false')]]></hint><hint><![CDATA[OPT_PARAM('_b_tr
ee_bitmap_plans' 'false')]]></hint><hint><![CDATA[DB_VERSION('')]]></hin


In the above query of plan table, I am assuming there will be only 1 plan.

Now we got the required plan in the form of XML which we used to get from V$SQL_PLAN.OTHER_XML

All we have to do now if follow same steps to parse this PLAN_TABLE.OTHER_XML column, get the hints and apply to original SQL.

Following are the steps

Get the required hints. Please note that I have used PLAN_TABLE now instead of V$SQL_PLAN. Also no need to give any SQL ID here as its going to get the hints of the query for which we did “explain plan for”

select  CHR(9)||''''
        || ''','
from    xmltable('/*/outline_data/hint'
                passing (select     xmltype(other_xml) as xmlval
                        from        PLAN_TABLE
                        where       other_xml is not null
                        and         rownum < 2)) d;

So in this case I get following hints

    'INDEX_FFS(@"SEL$1" "T"@"SEL$1" ("T"."DATA_OBJECT_ID"))',
    'OPT_PARAM(''optimizer_index_caching'' 80)',
    'OPT_PARAM(''optimizer_index_cost_adj'' 10000)',
    'OPT_PARAM(''_optimizer_connect_by_cost_based'' ''false'')',
    'OPT_PARAM(''_optim_peek_user_binds'' ''false'')',
    'OPT_PARAM(''_b_tree_bitmap_plans'' ''false'')',

Rest of the procedure remains the same. You can use either V$SQL_PLAN and V$SQL or DBA_HIST_SQL_PLAN and DBA_HIST_SQLTEXT where ever the query is present so that profile will get generated.

Following PLSQL code can be used

    ar_profile_hints sys.sqlprof_attr;
    ar_profile_hints := sys.sqlprof_attr(
    'OPT_PARAM(''_b_tree_bitmap_plans'' ''false'')',
    'OPT_PARAM(''_optim_peek_user_binds'' ''false'')',
    'OPT_PARAM(''_optimizer_connect_by_cost_based'' ''false'')',
    'OPT_PARAM(''optimizer_index_cost_adj'' 10000)',
    'OPT_PARAM(''optimizer_index_caching'' 80)',
    'INDEX_RS_ASC(@"SEL$1" "T"@"SEL$1" ("T"."DATA_OBJECT_ID"))',
    for sql_rec in (
    select t.sql_id, t.sql_text
    from v$sql t, v$sql_plan p
    where t.sql_id = p.sql_id
    and p.sql_id = '&SQL_ID_TO_FIX'
    and p.plan_hash_value = &BAD_PLAN_HASH_VALUE
    and p.parent_id is null
    sql_text    => sql_rec.sql_text,
    profile     => ar_profile_hints,
    name        => 'PROFILE_&&SQL_ID_TO_FIX');

end loop;
Enter value for sql_id_to_fix: cj4sqr25b6b8k
Enter value for bad_plan_hash_value: 2966233522
Enter value for sql_id_to_fix: cj4sqr25b6b8k

PL/SQL procedure successfully completed.

We basically tried to get the hints of a different plan (using index) just by using “explain plan for” to get the required hints from PLAN_TABLE. We then forced those hints on original SQL to create a profile so that original SQL will start using index.

Hope above procedure if clear in understanding !!

2 thoughts on “Fixing SQL Plans: The hard way – Part 3

  1. Hi ,

    Thanks for such a useful script. The script works fine for most of the queries. But queries with longer hint is giving me trouble. I am getting the error – ORA-06502: PL/SQL: numeric or value error: character string buffer too small. Is there any way we can increase the size of the var. ar_profile_hints which is of sys.sqlprof_attr type or any other workaround possible.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s