选择顶行时使用错误的索引

时间:2011-08-02 11:38:00

标签: sql oracle optimization plsql indexing

我有一个简单的查询,它选择由其他索引列过滤的其中一列排序的前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

计划显示它使用索引CR_PROPOSALSEARCH_I1,它是两列的索引:PROPOSALNUMBER&amp; UPPER(CUSTOMERNAME),执行 0.985s query with ROWNUM

如果我摆脱了ROWNUM条件,该计划就是我所期望的,它在 0.343s 中执行: query without ROWNUM

index XIF25CR_PROPOSALSEARCH is on CR_PROPOSALSEARCH (UPPER(CUSTOMERPOSTCODE));

怎么回事?

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

4 个答案:

答案 0 :(得分:8)

包括ROWNUM会更改优化程序关于哪个是更有效路径的计算。

当您执行这样的前n个查询时,并不一定意味着Oracle将获取所有行,对它们进行完全排序,然后返回顶部行。执行计划中的COUNT STOPKEY操作表明Oracle只会在找到您要求的行数之前执行基础操作。

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

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

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

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

答案 1 :(得分:4)

如果您确定您的统计数据是最新的且索引足够有选择性,您可以告诉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

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

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

例如:索引范围扫描的成本可能会偏离

忘了提.... 你应该检查实际的基数:

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

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

答案 2 :(得分:1)

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

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

由于任何一个索引都是权衡(一个更适合排序,另一个更适合应用WHERE子句),ROWNUM等细节决定了Oracle选择的执行计划。

答案 3 :(得分:1)

这个条件:

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

不是连续的,也就是说你不能为它保留一个有序的范围。

因此,有两种方法可以执行此查询:

  1. 按编号排序,然后过滤代码。
  2. 过滤代码,然后按编号排序。
  3. 方法1能够使用数字索引,为您提供线性执行时间(前100行将比2更快200次提供该数字和代码不相关)。

    方法2能够使用范围扫描对代码进行粗略过滤(范围条件类似于code >= 'MK3' AND code < 'MK4'),但是,由于数字顺序无法保留,因此需要排序在综合指数中。

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

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

    这意味着存在一个临界点:对于这种情况:ROWNUM <= 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字母的每组代码,将单独保留数字顺序,这将为您提供更密集的索引扫描。