Oracle ROWNUM性能

Baz*_*azi 8 sql oracle performance

要查询Oracle中的前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;
Run Code Online (Sandbox Code Playgroud)

但是对于非常大的桌子来说,效率很低 - 对我来说它运行大约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;
Run Code Online (Sandbox Code Playgroud)

为了弄清楚发生了什么,我查看了每个查询的执行计划.对于第一个查询:

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   
Run Code Online (Sandbox Code Playgroud)

第二个:

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  
Run Code Online (Sandbox Code Playgroud)

显然,INDEX FULL SCAN DESCENDING会使大型表的第一个查询效率低下.但我无法通过查看它们来区分两个查询的逻辑.谁能解释一下人类语言中两个查询之间的逻辑差异?

提前致谢!

Dav*_*sta 3

首先,正如 Alex 的评论中提到的,我不确定你的第二个版本是否 100% 保证为你提供正确的行——因为查询的“中间”块没有显式的order by,Oracle 没有以任何特定顺序将行传递到外部查询块的义务。但是,似乎没有任何特殊原因会改变行从最里面的块向上传递的顺序,因此在实践中它可能会起作用。

这就是 Oracle 为第二个查询选择不同计划的原因——逻辑上它无法将该STOPKEY操作应用于最里面的查询块。

我认为在第一种情况下,优化器假设id值分布良好,并且对于任何给定值,可能存在一些最近的事务。由于它可以看到它只需要找到最近的 5 个匹配项,因此它计算出使用索引按降序扫描行似乎更有效,paydate从表中查找相应的 id 和其他数据,然后停止当找到前 5 个匹配项时。我怀疑您会看到此查询的性能非常不同,具体取决于您使用的特定 id 值 - 如果 id 最近有很多活动,则应该很快找到行,但如果没有,则索引扫描可能需要做更多的工作。

STOPKEY在第二种情况下,我认为由于额外的嵌套层,它无法将优化应用于最里面的块。在这种情况下,索引完整扫描的吸引力就会大大降低,因为它总是需要扫描整个索引。因此,它选择对(我假设)进行索引查找id,然后对日期进行实际排序。如果给定的id值与一小部分行匹配,这可能会更有效——但是如果你给出一个id在整个表中分布有很多行的值,我预计它会变得更慢,因为它必须访问并对许多行进行排序。

因此,我猜测您的测试使用的id值的行相对较少,而且不是最近的。如果这是一个典型的用例,那么第二个查询可能更适合您(再次强调,我不确定它在技术上是否能保证产生正确的结果集)。但是,如果典型值更有可能有许多匹配行和/或更可能有 5 个最近的行,那么第一个查询和计划可能会更好。