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会使大型表的第一个查询效率低下.但我无法通过查看它们来区分两个查询的逻辑.谁能解释一下人类语言中两个查询之间的逻辑差异?
提前致谢!
首先,正如 Alex 的评论中提到的,我不确定你的第二个版本是否 100% 保证为你提供正确的行——因为查询的“中间”块没有显式的order by,Oracle 没有以任何特定顺序将行传递到外部查询块的义务。但是,似乎没有任何特殊原因会改变行从最里面的块向上传递的顺序,因此在实践中它可能会起作用。
这就是 Oracle 为第二个查询选择不同计划的原因——逻辑上它无法将该STOPKEY操作应用于最里面的查询块。
我认为在第一种情况下,优化器假设id值分布良好,并且对于任何给定值,可能存在一些最近的事务。由于它可以看到它只需要找到最近的 5 个匹配项,因此它计算出使用索引按降序扫描行似乎更有效,paydate从表中查找相应的 id 和其他数据,然后停止当找到前 5 个匹配项时。我怀疑您会看到此查询的性能非常不同,具体取决于您使用的特定 id 值 - 如果 id 最近有很多活动,则应该很快找到行,但如果没有,则索引扫描可能需要做更多的工作。
STOPKEY在第二种情况下,我认为由于额外的嵌套层,它无法将优化应用于最里面的块。在这种情况下,索引完整扫描的吸引力就会大大降低,因为它总是需要扫描整个索引。因此,它选择对(我假设)进行索引查找id,然后对日期进行实际排序。如果给定的id值与一小部分行匹配,这可能会更有效——但是如果你给出一个id在整个表中分布有很多行的值,我预计它会变得更慢,因为它必须访问并对许多行进行排序。
因此,我猜测您的测试使用的id值的行相对较少,而且不是最近的。如果这是一个典型的用例,那么第二个查询可能更适合您(再次强调,我不确定它在技术上是否能保证产生正确的结果集)。但是,如果典型值更有可能有许多匹配行和/或更可能有 5 个最近的行,那么第一个查询和计划可能会更好。