Oracle Optimizer 无关过滤谓词?

问题描述

为什么即使在同一索引的访问谓词保证过滤谓词始终为真之后,Oracle 仍然对索引应用过滤谓词?

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? 

鉴于 access("C"."CODE"="P"."CODE") 保证 C.code'A''Z',为什么需要过滤谓词 5?

提前致谢。

Oracle 12.1 企业版。

解决方法

这是“传递闭包”转换的结果:您可以在此处阅读更多信息:

  1. Transitivity and Transitive Closure (Doc ID 68979.1) Doc id 68979.1
  2. Jonathan Lewis - Cartesian Merge Join
  3. Jonathan Lewis - Transitive Closure(或者,甚至更好,在他的书中"Cost Based Oracle Fundamentals"

如果您获得 CBO 跟踪(rgb(255,0)alter session set events '10053 trace name context forever,level 1'),您将看到转换发生在选择连接方法和访问路径之前。它允许 CBO 分析更多不同的访问路径并选择最佳可用计划。此外,在自适应计划的情况下,它允许 oracle 即时更改连接方法。 例如,您可以获得这样的计划:

alter session set events 'trace[SQL_Optimizer.*]

事实上,您可以使用事件 ---------------------------------------------------------------------------------------------------------------- | 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') 禁用它。

你的例子:

10155: CBO disable generation of transitive OR-chains

结果:

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');

如您所见,该谓词已消失。

附注。传递谓词的其他事件:

  • ORA-10155:CBO 禁用传递 OR 链的生成
  • ORA-10171:CBO 禁用传递连接谓词
  • ORA-10179:CBO 关闭传递谓词替换
  • ORA-10195:CBO 不对传递谓词使用检查约束