【问题标题】:Wrong index being used when selecting top rows选择顶行时使用了错误的索引
【发布时间】:2011-10-18 04:37:45
【问题描述】:

我有一个简单的查询,它选择由其他索引列过滤的列之一排序的前 200 行。令人困惑的是为什么 PL/SQL Developer 中的查询计划显示,当我选择所有行时,使用此索引,例如:

SELECT * FROM
(
 SELECT *
 FROM cr_proposalsearch ps
 WHERE UPPER(ps.customerpostcode) like 'MK3%'
 ORDER BY ps.ProposalNumber DESC
)
WHERE ROWNUM <= 200

Plan 显示它使用索引 CR_PROPOSALSEARCH_I1,它是两列上的索引:PROPOSALNUMBER & UPPER(CUSTOMERNAME),这需要 0.985s 来执行:

如果我摆脱了 ROWNUM 条件,那么计划就是我所期望的,它会在 0.343s 内执行:

在哪里index XIF25CR_PROPOSALSEARCH is on CR_PROPOSALSEARCH (UPPER(CUSTOMERPOSTCODE));

怎么会?

编辑:我收集了cr_proposalsearch 表的统计信息,现在两个查询计划都显示它们使用XIF25CR_PROPOSALSEARCH 索引。

【问题讨论】:

  • 两个不同的查询,两个不同的结果集,为什么不应该有两个不同的解释计划?

标签: sql oracle optimization plsql indexing


【解决方案1】:

包含 ROWNUM 会改变优化器的计算,即哪个是更有效的路径。

当您执行这样的 top-n 查询时,并不一定意味着 Oracle 会获取所有行,对它们进行完全排序,然后返回顶部的行。执行计划中的COUNT STOPKEY 操作表示Oracle 只会执行底层操作,直到找到您要求的行数。

优化器计算出完整查询将获取并排序 77K 行。如果它将此计划用于前 n 个查询,则必须对这些行进行大量排序才能找到前 200 行(不一定必须对它们进行完全排序,因为它不关心确切的顺序超过顶部的行数;但它必须查看所有这些行)。

top-n 查询的计划使用另一个索引来完全避免排序。它按顺序考虑每一行,检查它是否与谓词匹配,如果匹配则返回它。当它返回 200 行时,它就完成了。它的计算表明,这对于获取少量行将更有效。 (当然,这可能不正确;您还没有说这些查询的相对性能是什么。)

如果优化器在您请求所有行时选择此计划,则它必须按降序读取整个索引,在检查谓词时按 ROWID 从表中获取每一行。这将导致大量额外的 I/O 并检查许多不会返回的行。所以在这种情况下,它决定使用customerpostcode 上的索引更有效。

如果您逐渐增加从 top-n 查询返回的行数,您可能会发现计划从第一个切换到第二个的临界点。仅从这两个计划的成本来看,我猜这可能是 1,200 行左右。

【讨论】:

  • 天才,门槛是1166,你怎么知道的?我添加了其他索引的时间和详细信息。如果您按照@Kevin Burton 的回答告诉查询使用索引,则时间始终在 0.3 秒左右
  • 请注意,我的最大 rownum 数将始终为 200。这意味着永远不会使用 XIF25CR_PROPOSALSEARCH?我可以在这里使用其他优化选项吗?
  • 我猜第一个计划的成本会线性增加。 6 x 951 = 5,706,非常接近第二个计划的成本 5,512。所以我估计临界点将接近 6 x 200 = 1200。
【解决方案2】:

如果您确定您的统计数据是最新的并且索引具有足够的选择性,您可以告诉 oracle 使用该索引

SELECT  *
FROM   (SELECT /*+ index(ps XIF25CR_PROPOSALSEARCH) */  *
        FROM     cr_proposalsearch ps
        WHERE    UPPER (ps.customerpostcode) LIKE 'MK3%'
        ORDER BY ps.proposalnumber DESC)
WHERE  ROWNUM <= 200

(我只推荐这种方法作为最后的手段)

如果我这样做,我会首先 tkprof 查询以查看它实际做了多少工作,

例如:索引范围扫描的成本可能会大大降低

忘了说.... 您应该检查实际的基数:

SELECT count(*)  FROM cr_proposalsearch ps  WHERE UPPER(ps.customerpostcode) like 'MK3%' 

然后将其与查询计划中的基数进行比较。

【讨论】:

    【解决方案3】:

    您似乎没有完美的索引。索引 CR_PROPOSALSEARCH_I1 可用于按属性 PROPOSALNUMBER 的降序检索行。选择它可能是因为 Oracle 可以避免检索所有匹配的行,根据 ORDER BY 子句对其进行排序,然后丢弃除第一行之外的所有行。

    没有 ROWNUM 条件,Oracle 使用 XIF25CR_PROPOSALSEARCH 索引(您没有提供有关它的任何详细信息),因为它可能对 WHERE 子句具有相当的选择性。但它需要事后对结果进行排序。基于您将检索所有行的假设,这可能是更有效的计划。

    由于任一索引都需要权衡(一个更适合排序,另一个更适合应用 WHERE 子句),ROWNUM 等详细信息决定了 Oracle 选择哪个执行计划。

    【讨论】:

      【解决方案4】:

      这个条件:

      WHERE UPPER(ps.customerpostcode) like 'MK3%'
      

      不是连续的,即您不能为其保留单个有序范围。

      所以有两种方法可以执行这个查询:

      1. 按编号排序,然后按代码过滤。
      2. 过滤代码,然后按编号排序。

      方法 1 能够使用数字索引,从而为您提供线性执行时间(选择顶部 100 行将比顶部 200 快倍,前提是数字和代码不相关) .

      方法2 能够使用范围扫描对代码进行粗略过滤(范围条件类似于code &gt;= 'MK3' AND code &lt; 'MK4'),但是,它需要排序,因为不能在复合索引中保留数字的顺序.

      排序时间也取决于您选择的顶部行数,但与方法 1 不同,这种依赖性不是线性的(您总是需要至少一次范围扫描)。

      但是,2 方法中的过滤条件对RANGE SCAN 具有足够的选择性,随后对整个表进行排序比FULL SCAN 更有效。

      这意味着存在一个临界点:对于这种情况:ROWNUM &lt;= X 存在一个值 X,因此当超过该值时,方法 2 变得更有效。

      更新:

      如果您总是至少搜索3 的第一个符号,您可以创建如下索引:

      SUBSTRING(UPPER(customerpostcode), 1, 3), proposalnumber
      

      并在此查询中使用它:

      SELECT  *
      FROM    (
              SELECT  *
              FROM    cr_proposalsearch ps
              WHERE   SUBSTRING(UPPER(customerpostcode, 1, 3)) = SUBSTRING(UPPER(:searchquery), 1, 3)
                      AND UPPER(ps.customerpostcode) LIKE UPPER(:searchquery) || '%'
              ORDER BY
                      proposalNumber DESC
              )
      WHERE   rownum <= 200
      

      这样,对于共享第一个3 字母的每组代码,将分别保留编号顺序,这将为您提供更密集的索引扫描。

      【讨论】:

        猜你喜欢
        • 2012-01-02
        • 1970-01-01
        • 1970-01-01
        • 2015-08-03
        • 2017-10-24
        • 1970-01-01
        • 2014-04-18
        • 2016-05-03
        • 1970-01-01
        相关资源
        最近更新 更多