Skip to Main Content
  • Questions
  • is the search_columns not part of building the plan_hash_value?

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Dirk.

Asked: November 16, 2016 - 12:13 pm UTC

Last updated: November 24, 2016 - 2:01 pm UTC

Version: 12.1

Viewed 1000+ times

You Asked

Hello there,

I have a table with a composite primary key over 3 columns (a,b,c). Now there are 2 typical queries running:

select * from my_table t where t.a = ? and t.b = ?

select * from my_table t where t.a = ? and t.b between ? and ? and t.c = ?


I notice that both queries share the same plan_hash_value in dba_hist_sql_plan. In both cases the optimizer is doing a range scan over my primary key index. But in the first case he is using 2 search_columns and makes a range scan over the third column. In the second query, he's using 3 search_columns making a range scan. Apparently, different values in the column "search_columns" does not make different hash values.

Now what worries me: We have some performance variations when querying for the exact 3 columns of the primary key. When I look at the dba_his_sql_plan I notice that even though we are querying for all 3 columns of a primary key / unique index, we experience a range scan. The data in dba_hist_sql_plan shows a value of "2" in search_columns. Now this wouldn't be troublesome because the last column has a low selectivity. But during the times we face bad performance, I fear that the optimizer is only using 1 search_column. Is there any way to figure that out?

I assume that if the optimizer decides to only use 1 search column and make a range scan over the rest, he would build an execution plan, calculate its hash value and then make the statistics available in dba_hist_sqlstat. For me there is no way to notice that he actually did something else (use 1 instead of 2 search columns) because it points to the same plan_hash_value.

Am I making this up?

and Chris said...

No. The plan_hash_value depends on the shape of the plan. Not the predicates!

For example, all the following queries do a full table scan. So all the plans have the same hash value:

create table t (
  x int,
  y int, 
  z int
);

insert into t 
  select rownum, rownum*2, mod(rownum, 7) from dual
  connect by level <= 100;
  
commit;
exec dbms_stats.gather_table_stats(user, 't');

set serveroutput off
select /*+   gather_plan_statistics */* from t;

select * from table(dbms_xplan.display_cursor(null, null, 'BASIC +PREDICATE'));

PLAN_TABLE_OUTPUT                               
EXPLAINED SQL STATEMENT:                        
------------------------                        
select /*+   gather_plan_statistics */* from t  
                                                
Plan hash value: 2498539100                     
                                                
----------------------------------              
| Id  | Operation         | Name |              
----------------------------------              
|   0 | SELECT STATEMENT  |      |              
|   1 |  TABLE ACCESS FULL| T    |              
----------------------------------  

select /*+   gather_plan_statistics */* from t
where  x = 1;

select * from table(dbms_xplan.display_cursor(null, null, 'BASIC +PREDICATE'));

PLAN_TABLE_OUTPUT                                            
EXPLAINED SQL STATEMENT:                                     
------------------------                                     
select /*+   gather_plan_statistics */* from t where  x = 1  
                                                             
Plan hash value: 2498539100                                  
                                                             
----------------------------------                           
| Id  | Operation         | Name |                           
----------------------------------                           
|   0 | SELECT STATEMENT  |      |                           
|*  1 |  TABLE ACCESS FULL| T    |                           
----------------------------------                           
                                                             
Predicate Information (identified by operation id):          
---------------------------------------------------          
                                                             
   1 - filter("X"=1)

select /*+   gather_plan_statistics */* from t
where  z = 2
and    y between 4 and 128;

select * from table(dbms_xplan.display_cursor(null, null, 'BASIC +PREDICATE'));

PLAN_TABLE_OUTPUT                                                      
EXPLAINED SQL STATEMENT:                                               
------------------------                                               
select /*+   gather_plan_statistics */* from t where  z = 2 and    y   
between 4 and 128                                                      
                                                                       
Plan hash value: 2498539100                                            
                                                                       
----------------------------------                                     
| Id  | Operation         | Name |                                     
----------------------------------                                     
|   0 | SELECT STATEMENT  |      |                                     
|*  1 |  TABLE ACCESS FULL| T    |                                     
----------------------------------                                     
                                                                       
Predicate Information (identified by operation id):                    
---------------------------------------------------                    
                                                                       
   1 - filter(("Z"=2 AND "Y"<=128 AND "Y">=4))


But clearly the performance between these could be very different. One returns all the rows (no where clause), another returns just one row (where x = 1)!

On to your issue. To see exactly which conditions Oracle is applying at which stage, get an execution plan.

Use the select * from table(dbms...) query I used above, ensuring you have the +PREDICATE option. This will show you which conditions were applied where. You'll probably also want to see how many rows Oracle processed at each step. Use "ALLSTATS LAST" instead of BASIC to do this:

select * 
from table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST +PREDICATE'));


For more about creating execution plans, read:

https://blogs.oracle.com/sql/entry/how_to_create_an_execution

If you need further help, please post the plans from your queries.

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

I think you misunderstood my question

Dirk Haase, November 24, 2016 - 1:50 pm UTC

Thanks for the response. But I think you misunderstood my question. I understand that different predicates result in different operations and different performance. But that was not the question. So let me try to rephrase the question.

I noticed that when the optimizer decides to use an index range scan, the number of search_columns does not affect the plan_hash_value. If the database uses 1 or 2 search_columns of a 3-column index, in both cases I will notice the same value of plan_hash_value in dba_hist_sqlstat. So when I look at last night's Performance in dba_hist_sqlstat, and I see Statement X executed with plan_hash_value Y in snapshot 1 and 2. But the average execution time in snapshot 2 is significantly higher, I have no way to tell if the optimizer used 1 or 2 search_columns in the index range scan.

What I fear what happend for us: The first time Statement X was executed and the optimizer decided to use 2 search_columns of our 3-column index, the execution plan was built, its plan_hash_value calculated and stored in dba_hist_sql_plan. At a later time, the optimizer decides to use only 1 search_column of the index. But since this results in the same plan_hash_value, I have no way to tell that this happens. When I look in dba_hist_sql_plan, it still shows me the value of "2" in search_columns although this is not correct.
Chris Saxon
November 24, 2016 - 2:01 pm UTC

So... what is your question?

If you want to see what happened, you need to get the execution plan for that run of the statement.

Probably an obvious explanation

Andrew Sayer, November 24, 2016 - 11:58 pm UTC

What you're describing sounds rather strange, if you are filtering on all the columns within the index using equality predicates then there's no obvious reason why the optimizer will decide it's best to only use some of the columns and then do the rest of the filtering on the table blocks. It's more likely that it has not been able to apply a filter on the index in some way.

I few things come to mind:
Your filters are not pure equality filters, remember if any predicates are non-equality then only the columns up to this point will be used to determine what part of the index to read.
You're using a function on the column and filtering that (possibly due to an implicit data type conversion)
You have extra columns in your index that you aren't filtering on.
You have strange optimizer parameters
You have an undocumented parameter set
You've misunderstood what's going on and it's a lot more simple.

You can check what child cursors are being used and see if they are different. It is highly unlikely (and I don't think possible) that a plan was created and then sometimes executed with less filters than before for the same query. There is an undocumented optimizer parameter which prevents the access and filter predicates from being recorded in v$sql_plan but, again, I don't think it would cause a difference.

Could you share the real query, the complete DDL for the primary key/unique index and the complete execution plans displayed using dbms_xplan.display_cursor supplying the sql_id.

More to Explore

Performance

Get all the information about database performance in the Database Performance guide.