1

我们有两个这样的表:

Event
    id
    type
    ... a bunch of other columns

ProcessedEvent
    event_id
    process

有定义的索引

  • 事件(id)(PK)
  • ProcessedEvent (event_id, 进程)

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

第二个表示某个事件被某个进程处理的事实。有很多进程需要处理某个事件,因此第二个表中的每个条目对应第一个表中的多个条目。

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

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 索引进行全索引扫描
  • 对事件索引进行全索引扫描
  • 两者之间的反连接
  • 与其余的表访问
  • 筛选

相反,Oracle 执行以下操作

  • 对 ProcessedEvent 索引进行全索引扫描
  • 事件表上的全表扫描
  • 过滤事件表
  • 两组之间的反连接

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

  • 对 ProcessedEvent 索引进行全索引扫描
  • 对事件索引进行全索引扫描
  • 事件表上的表访问
  • 过滤事件表
  • 两组之间的反连接

恕我直言,这真的很愚蠢。

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


补充:性能很差。我们通过仅选择 Event.IDs 然后“手动”获取所需的行来解决性能问题。但这当然只是一种解决方法。

4

4 回答 4

2

您的 FULL INDEX SCAN 可能会比 FULL TABLE SCAN 更快,因为索引可能比表“更薄”。尽管如此,FULL INDEX SCAN 是完整的分段读取,其成本与 FULL TABLE SCAN 大致相同。

但是,您还添加了 TABLE ACCESS BY ROWID 步骤。这是一个代价高昂的步骤:每行一个逻辑 IO用于 ROWID 访问,而对于 FULL TABLE SCAN,每个多块(取决于您的)获得一个逻辑 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 不知道事件是什么类型),因此减少了将被反连接的集合的大小(然而全表扫描的另一个优点)。

于 2010-01-13T14:29:15.107 回答
0

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

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

性能很差,还是您只是在问优化器为什么这样做?

于 2010-01-13T14:27:11.587 回答
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
)

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

于 2010-01-13T14:37:22.453 回答
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)。

于 2013-10-10T13:52:20.703 回答