如何加快 Oracle 中的 row_number?
Posted
技术标签:
【中文标题】如何加快 Oracle 中的 row_number?【英文标题】:How can I speed up row_number in Oracle? 【发布时间】:2010-10-24 01:10:59 【问题描述】:我有一个如下所示的 SQL 查询:
SELECT * FROM(
SELECT
...,
row_number() OVER(ORDER BY ID) rn
FROM
...
) WHERE rn between :start and :end
本质上,是 ORDER BY 部分让事情变慢了。如果我要删除它,EXPLAIN 成本会下降一个数量级(超过 1000 倍)。我试过这个:
SELECT
...
FROM
...
WHERE
rownum between :start and :end
但这并没有给出正确的结果。有什么简单的方法可以加快速度吗?还是我必须花更多时间使用 EXPLAIN 工具?
【问题讨论】:
【参考方案1】:ROW_NUMBER
在Oracle
中效率很低。
有关性能详情,请参阅我博客中的文章:
Oracle: ROW_NUMBER vs ROWNUM对于您的特定查询,我建议您将其替换为 ROWNUM
并确保使用索引:
SELECT *
FROM (
SELECT /*+ INDEX_ASC(t index_on_column) NOPARALLEL_INDEX(t index_on_column) */
t.*, ROWNUM AS rn
FROM table t
ORDER BY
column
)
WHERE rn >= :start
AND rownum <= :end - :start + 1
此查询将使用COUNT STOPKEY
还要确保您的 column
不可为空,或者添加 WHERE column IS NOT NULL
条件。
否则索引不能用于检索所有值。
请注意,您不能在没有子查询的情况下使用 ROWNUM BETWEEN :start and :end
。
ROWNUM
总是最后分配,最后检查,这样ROWNUM
总是按顺序排列,没有间隙。
如果使用ROWNUM BETWEEN 10 and 20
,则满足所有其他条件的第一行将成为返回候选,临时分配ROWNUM = 1
并通过ROWNUM BETWEEN 10 AND 20
的测试。
那么下一行将是一个候选,分配ROWNUM = 1
和失败等等,所以,最后,根本不会返回任何行。
这应该通过将ROWNUM
's 放入子查询来解决。
【讨论】:
像魅力一样工作。但是,优化器提示似乎并没有产生明显的影响。 这意味着CBO
足够聪明,可以获取索引。实际上,这里重要的是 ROWNUM 而不是 ROW_NUMBER。
但我还是会留下提示或创建一个大纲,以防 CBO 改变主意:)
仅供参考,我还没有尝试过这两种方法,但是如果我尝试使用 FIRST_ROWS 而不是 INDEX_ASC 和 NOPARALLEL_INDEX 进行查询,解释成本从 ~25,000 变为 8 并且运行速度非常接近恒定时间(我可以像其中一个一样快地提取所有记录),所以我什至可能不需要再分页了。如果记录数突然激增,我可能仍会使用分页。
+1 但我遇到了一些困难才能让它工作。我不得不使用提到的博客文章explainextended.com/2009/05/06/oracle-row_number-vs-rownum 的最后一个示例(带有两个子查询)【参考方案2】:
在我看来像是一个分页查询。
来自这篇 ASKTOM 文章(大约在页面下方的 90%):
You need to order by something unique for these pagination queries, so that ROW_NUMBER is assigned deterministically to the rows each and every time.
此外,您的查询也不尽相同,所以我不确定比较一个和另一个的成本有什么好处。
【讨论】:
其实那篇文章帮我写了查询。不过,我没有注意到有关按唯一 ID 排序的部分。还有一个我错过的查询优化器提示。我明天上班试试! ;) 认为它看起来很熟悉。 first_rows 对分页查询非常有用。 那和 Quassnoi 的建议让我的查询时间几乎保持不变!我希望我可以选择两个答案。 :-(【参考方案3】:您的 ORDER BY 列是否已编入索引?如果不是,那是一个很好的起点。
【讨论】:
其实不是。但是将其更改为 IS 索引的行并没有帮助。感谢您提出明显的建议。 :-) 只有当访问路径可以使用该索引(即您正在查找一系列 ID)时,索引才会有助于改进 ORDER BY。【参考方案4】:部分问题在于“开始”到“结束”的跨度有多大以及它们“居住”在哪里。 假设您在表中有一百万行,并且您想要第 567,890 到 567,900 行,那么您将不得不接受这样一个事实,即它需要遍历整个表,几乎所有这些都按 id 排序,并计算出该范围内的行数。
简而言之,这是很多工作,这就是优化器付出高成本的原因。
这也不是索引可以提供太多帮助的东西。索引会给出顺序,但充其量只是给你一个开始的地方,然后你继续阅读,直到你到达第 567,900 个条目。
如果您一次向最终用户展示 10 个项目,实际上可能值得从数据库中获取前 100 个项目,然后让应用程序将这 100 个项目分成十个块。
【讨论】:
这听起来很合适。我真的从大约 200 万条记录中提取了大约 15,000 条记录。我们限制了查询可以花费的时间,并且一次提取所有 15k 条记录会导致超时。因此,我认为对结果进行分页可以防止这种情况发生。我想这只是意味着我将不得不经历要求更长暂停时间的官僚噩梦。 我希望您不会向用户发送 15,000 行数据!【参考方案5】:花更多时间使用 EXPLAIN PLAN 工具。如果您看到 TABLE SCAN,则需要更改查询。
您的查询对我来说毫无意义。查询 ROWID 似乎是自找麻烦。该查询中没有关系信息。是您遇到问题的真实查询还是您为说明问题而编造的示例?
【讨论】:
这是分页。这基本上就是查询至少对分页所做的。我刚刚取出了其余的查询(主要是因为它很重要)。为了简洁起见,所有的省略号都是我删减的地方。以上是关于如何加快 Oracle 中的 row_number?的主要内容,如果未能解决你的问题,请参考以下文章
ORACLE 中的 ROW_NUMBER() OVER() 分析函数的用法
ORACLE 中的 ROW_NUMBER() OVER() 分析函数的用法
ORACLE 中的 ROW_NUMBER() OVER() 分析函数的用法
Oracle数据库rownum和row_number的不同点