我有一个简单的查询,它选择由其他索引列过滤的其中一列排序的前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 :
如果我摆脱了ROWNUM条件,该计划就是我所期望的,它在 0.343s 中执行:
index XIF25CR_PROPOSALSEARCH is on CR_PROPOSALSEARCH (UPPER(CUSTOMERPOSTCODE));
怎么回事?
编辑:我收集了cr_proposalsearch
表的统计信息,现在两个查询计划都显示他们使用XIF25CR_PROPOSALSEARCH
索引。
答案 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
能够使用数字索引,为您提供线性执行时间(前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
字母的每组代码,将单独保留数字顺序,这将为您提供更密集的索引扫描。