如何加快 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_NUMBEROracle 中效率很低。

有关性能详情,请参阅我博客中的文章:

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的不同点

如何加快 Oracle SQL Developer 上的 REGEX LEVEL 查询

Informix 的 Row_number() 函数