要在Oracle中查询 top-n 行,通常使用ROWNUM。 所以以下查询似乎没问题(获得最近的5次付款):
select a.paydate, a.amount
from (
select t.paydate, t.amount
from payments t
where t.some_id = id
order by t.paydate desc
) a
where rownum <= 5;
但对于非常大的桌子来说,效率很低 - 对我来说它运行约10分钟。 所以我尝试了其他查询,最后我得到了一个运行时间不到一秒的查询:
select *
from (
select a.*, rownum
from (select t.paydate, t.amount
from payments t
where t.some_id = id
order by t.paydate desc) a
)
where rownum <= 5;
为了了解发生了什么,我查看了每个查询的执行计划。对于第一个查询:
SELECT STATEMENT, GOAL = ALL_ROWS 7 5 175
COUNT STOPKEY
VIEW 7 5 175
TABLE ACCESS BY INDEX ROWID 7 316576866 6331537320
INDEX FULL SCAN DESCENDING 4 6
第二名:
SELECT STATEMENT, GOAL = ALL_ROWS 86 5 175
COUNT STOPKEY
VIEW 86 81 2835
COUNT
VIEW 86 81 1782
SORT ORDER BY 86 81 1620
TABLE ACCESS BY INDEX ROWID 85 81 1620
INDEX RANGE SCAN 4 81
显然, INDEX FULL SCAN DESCENDING 会使大表的第一个查询效率低下。但我无法通过查看它们来区分两个查询的逻辑。 谁能解释一下人类语言中两个查询之间的逻辑差异?
提前致谢!
答案 0 :(得分:3)
首先,正如Alex的评论中提到的,我不确定你的第二个版本是否100%保证给你正确的行 - 因为查询的“中间”块没有明确的{{ 1}},Oracle没有义务按任何特定顺序将行传递到外部查询块。但是,似乎没有任何特殊原因会改变行从最里面的块传递的顺序,因此在实践中它可能会起作用。
这就是为什么Oracle为第二个查询选择不同的计划 - 它在逻辑上无法将order by
操作应用于最里面的查询块。
我认为在第一种情况下,优化器假设STOPKEY
值分布均匀,并且对于任何给定值,可能会有一些非常近期的事务。因为它可以看到它只需要查找最近的5个匹配项,所以它计算出使用索引以id
的降序扫描行似乎更有效,查找相应的id和其他数据来自桌子,并在发现前5场比赛时停止。我怀疑你会看到这个查询的性能差别很大,具体取决于你使用的具体id值 - 如果id有很多最近的活动,那么应该很快找到行,但如果没有,则索引扫描可能需要做更多的工作。
在第二种情况下,我认为由于额外的嵌套层,它无法将paydate
优化应用于最里面的块。在这种情况下,索引全扫描将变得不那么有吸引力,因为它总是需要扫描整个索引。因此,它选择在STOPKEY
(我假设)上进行索引查找,然后在日期进行实际排序。如果给定的id
值与一小部分行匹配,这可能会更有效 - 但是如果你给一个id
遍布整个表的行有很多行,我希望它变慢,因为它必须访问和排序许多行。
所以,我猜你的测试使用了id
个值相对较少但不是很近的行。如果这是一个典型的用例,那么第二个查询可能对你更好(同样,需要注意的是我不确定它在技术上是否能保证产生正确的结果集)。但是,如果典型值更可能有许多匹配的行和/或更可能有5个最近的行,那么第一个查询和计划可能会更好。