Oracle反加入执行计划问题

时间:2010-01-13 14:15:32

标签: oracle performance optimization anti-join

我们有两个这样的表:

Event
    id
    type
    ... a bunch of other columns

ProcessedEvent
    event_id
    process

定义了索引
  • 事件(id)(PK)
  • ProcessedEvent(event_id,process)

第一个代表应用程序中的事件。

第二个代表某个事件由某个过程获得过程的事实。有许多进程需要处理某个事件,因此第二个表中的第一个条目中有多个条目。

为了找到需要处理的所有事件,我们执行以下查询:

select * // of course we do name the columns in the production code
from Event
where type in ( 'typeA', 'typeB', 'typeC')
and id not in (
    select event_id
    from ProcessedEvent
    where process = :1  
)

统计数据是最新的

由于大多数事件都已处理,我认为最好的执行计划看起来应该是这样的

  • ProcessedEvent Index上的完整索引扫描
  • 事件索引的完整索引扫描
  • 两者之间的反连接
  • 使用其余的表访问
  • 过滤

相反,Oracle会执行以下操作

  • ProcessedEvent Index上的完整索引扫描
  • 事件表上的全表扫描
  • 过滤事件表
  • 两套之间的反连接

通过索引提示,Oracle让Oracle执行以下操作:

  • ProcessedEvent Index上的完整索引扫描
  • 事件索引的完整索引扫描
  • 事件表上的表格访问
  • 过滤事件表
  • 两套之间的反连接

这真是愚蠢的恕我直言。

所以我的问题是:oracle坚持早期表访问的原因可能是什么?


增加: 表现不好。我们通过仅选择Event.ID然后“手动”获取所需的行来解决性能问题。但当然这只是一种解决方法。

4 个答案:

答案 0 :(得分:2)

您的FULL INDEX SCAN可能比FULL TABLE SCAN更快,因为索引可能比表更“稀”。尽管如此,FULL INDEX SCAN是一个完整的段读数,它与FULL TABLE SCAN的成本大致相同。

但是,您还要添加TABLE ACCESS BY ROWID步骤。这是一个昂贵的步骤:ROWID访问的一个逻辑IO 每行,而每个多个块获得一个逻辑IO (取决于您的db_file_multiblock_read_count parameter)全桌扫描。

总之,优化器计算:

cost(FULL TABLE SCAN) < cost(FULL INDEX SCAN) + cost(TABLE ACCESS BY ROWID)

更新:FULL TABLE SCAN也比FULL INDEX SCAN路径更早启用类型过滤器(因为INDEX不知道事件的类型),因此减小了将被反连接的集合(FULL TABLE SCAN的另一个优点)。

答案 1 :(得分:0)

优化器做了很多事情,一开始没有意义,但它有它的原因。它们可能并非总是正确,但它们是可以理解的。

由于其大小,Event表可能更容易进行全扫描而不是通过rowid访问。可能是,按顺序读取整个表所涉及的IO操作要少得多,而不是读取位和碎片。

性能不好,或者您只是问为什么优化器会这样做?

答案 2 :(得分:0)

我无法解释优化器的行为,但我的经验是不惜一切代价避免“NOT IN”,而是用MINUS替换它,如下所示:

select * from Event
where id in (
  select id from Event where type in ( 'typeA', 'typeB', 'typeC')
 minus
  select id from ProcessedEvent
)

我已经看到了具有类似转换的查询性能的数量级。

答案 3 :(得分:0)

类似的东西:

WITH
  PROCEEDED AS
  (
    SELECT
      event_id
    FROM
      ProcessedEvent
    WHERE
      PROCESS = :1
  )
SELECT
  * // of course we do name the columns in the production code
FROM
  EVENT
LEFT JOIN PROCEEDED P
ON
  p.event_id = EVENT.event_id
WHERE
  type           IN ( 'typeA', 'typeB', 'typeC')
  AND p.event_id IS NULL; -- exclude already proceeded

可以足够快地工作(至少比NOT IN快得多)。