Search code examples
oracle-databaseoptimizationpredicatesql-execution-plan

Oracle Optimizer Extraneous Filter Predicate?


Why does Oracle still apply a filter predicate on an index even after the access predicate for that same index guarantees the filter predicate is always true?

drop table index_filter_child
;

drop table index_filter_parent
;

create table index_filter_parent 
  as
  select level id, chr(mod(level - 1, 26) + ascii('A')) code from dual connect by level <= 26
;

create table index_filter_child 
  as
  with
    "C" as (select chr(mod(level - 1, 26) + ascii('A')) code from dual connect by level <= 26)
    select rownum id, C1.code from C C1, C C2
;

exec dbms_stats.gather_table_stats('USER','INDEX_FILTER_PARENT')
;

exec dbms_stats.gather_table_stats('USER','INDEX_FILTER_CHILD')
;

create index ix_index_filter_parent on index_filter_parent(code)
;

create index ix_index_filter_child on index_filter_child(code)
;

select P.* 
  from index_filter_parent "P" 
       join index_filter_child "C" 
         on C.code = P.code
where P.code in('A','Z') --same result if we predicate instead on C.code in('A','Z')
;


--------------------------------------------------------------------------------------------------------------
| id  | Operation                     | name                         | rows  | Bytes | cost (%CPU)| time     |
--------------------------------------------------------------------------------------------------------------
|   0 | select statement              |                              |     5 |    35 |     4   (0)| 00:00:01 |
|   1 |  nested LOOPS                 |                              |     5 |    35 |     4   (0)| 00:00:01 |
|   2 |   INLIST ITERATOR             |                              |       |       |            |          |
|   3 |    table access by index ROWID| INDEX_FILTER_PARENT          |     2 |    10 |     2   (0)| 00:00:01 |
|*  4 |     index range scan          | IX_INDEX_FILTER_PARENT       |     2 |       |     1   (0)| 00:00:01 |
|*  5 |   index range scan            | IX_INDEX_FILTER_CHILD        |     2 |     4 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("P"."CODE"='A' or "P"."CODE"='Z')
   5 - access("C"."CODE"="P"."CODE")
       filter("C"."CODE"='A' or "C"."CODE"='Z')   <========== why is this needed? 

Why is the filter predicate in 5 needed in light of the access("C"."CODE"="P"."CODE") guaranteeing C.code is 'A' or 'Z'?

Thank you in advance.

Oracle 12.1 enterprise Edition.


Solution

  • This is a result of "transitive closure" transformation: you can read more about here:

    1. Transitivity and Transitive Closure (Doc ID 68979.1) Doc id 68979.1
    2. Jonathan Lewis - Cartesian Merge Join
    3. Jonathan Lewis - Transitive Closure (or, even better, in his book "Cost Based Oracle Fundamentals")

    If you get CBO trace (alter session set events '10053 trace name context forever, level 1' or alter session set events 'trace[SQL_Optimizer.*]), you will see that the transformation happens before choosing join method and access paths. It allows CBO to analyze more different access paths and choose the best available plan. Moreover, in case of adaptive plans, it allows oracle to change join method on the fly. For example, you can get a plan like this:

    ----------------------------------------------------------------------------------------------------------------
    | Id  | Operation                             | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                      |                        |    52 |   364 |     4   (0)| 00:00:01 |
    |*  1 |  HASH JOIN                            |                        |    52 |   364 |     4   (0)| 00:00:01 |
    |   2 |   INLIST ITERATOR                     |                        |       |       |            |          |
    |   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| INDEX_FILTER_PARENT    |     2 |    10 |     2   (0)| 00:00:01 |
    |*  4 |     INDEX RANGE SCAN                  | IX_INDEX_FILTER_PARENT |     2 |       |     1   (0)| 00:00:01 |
    |   5 |   INLIST ITERATOR                     |                        |       |       |            |          |
    |*  6 |    INDEX RANGE SCAN                   | IX_INDEX_FILTER_CHILD  |    52 |   104 |     2   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - access("C"."CODE"="P"."CODE")
       4 - access("P"."CODE"='A' OR "P"."CODE"='Z')
       6 - access("C"."CODE"='A' OR "C"."CODE"='Z')
    

    In fact, you can disable it using the event 10155: CBO disable generation of transitive OR-chains.

    Your example:

    alter session set events '10155';
    explain plan for
    select P.* 
      from index_filter_parent "P" 
           join index_filter_child "C" 
             on C.code = P.code
    where P.code in('A','Z');
    

    Results:

    SQL> alter session set events '10155';
    
    Session altered.
    
    SQL> explain plan for
      2  select P.*
      3    from index_filter_parent "P"
      4         join index_filter_child "C"
      5           on C.code = P.code
      6  where P.code in('A','Z') ;
    
    Explained.
    
    SQL> @xplan typical
    
    
    PLAN_TABLE_OUTPUT
    ----------------------------------------------------------------------------------------------------------------
    Plan hash value: 2543178509
    
    ----------------------------------------------------------------------------------------------------------------
    | Id  | Operation                             | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                      |                        |    52 |   364 |     4   (0)| 00:00:01 |
    |   1 |  NESTED LOOPS                         |                        |    52 |   364 |     4   (0)| 00:00:01 |
    |   2 |   INLIST ITERATOR                     |                        |       |       |            |          |
    |   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| INDEX_FILTER_PARENT    |     2 |    10 |     2   (0)| 00:00:01 |
    |*  4 |     INDEX RANGE SCAN                  | IX_INDEX_FILTER_PARENT |     2 |       |     1   (0)| 00:00:01 |
    |*  5 |   INDEX RANGE SCAN                    | IX_INDEX_FILTER_CHILD  |    26 |    52 |     1   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       4 - access("P"."CODE"='A' OR "P"."CODE"='Z')
       5 - access("C"."CODE"="P"."CODE")
    
    Note
    -----
       - this is an adaptive plan
    
    22 rows selected.
    

    As you can see, that predicate has disappeared.

    PS. Other events for transitive predicates:

    • ORA-10155: CBO disable generation of transitive OR-chains
    • ORA-10171: CBO disable transitive join predicates
    • ORA-10179: CBO turn off transitive predicate replacement
    • ORA-10195: CBO don't use check constraints for transitive predicates