2

当需要在网站中进行分页时...哪种方法性能更好?

分析函数 - ROW_NUMBER()

行号

  • http://www.oracle.com/technetwork/issue-archive/2007/07-jan/o56asktom-086197.html
  • INMHO 我发现这种方法的代码更易读

    SELECT * FROM (
      SELECT rownum rn, a.* 
      FROM(
        SELECT columnA, columnB
        FROM table 
        ORDER BY columnB
      ) a 
      WHERE rn <= OFFSET
    )
    WHERE rnum >= LOW_LIMIT
    
    • 注意:我知道有 RANK 和 DENSE_RANK 分析函数,但假设我只需要通过确定性查询进行分页。

    • 注2:要检索我正在考虑使用单独的简单查询计数的记录总数(*)

4

3 回答 3

7

我觉得这个问题很有趣,所以我尝试了一些方法。

我有一个名为 large_t 的表,其中包含大约 110 万行。

然后我有两个查询:

select * 
from
(
  select rownum rnum, a.*
  from (
         select owner, object_name, object_id
         from large_t
         order by object_id
       ) a
  where rownum   <= 30      
) where rnum > 20;

select *
from
(
select owner, object_name, object_id,
       row_number() over (order by object_id) rnum
from large_t
) where rnum > 20 and rnum <= 30;

如果您查看两个查询生成的计划,第一个查询有一个操作:

SORT ORDER BY STOPKEY

虽然分析查询包含一个名为

WINDOW SORT PUSHED RANK

SORT ORDER BY STOPKEY 是一种比普通 ORDER BY 更有效的排序操作。我不确定 WINDOW SORT PUSHED RANK 是如何工作的,但它似乎以类似的方式工作。

运行两个查询后查看 v$sql_workarea,两者都只需要 4096 字节的 sort_area。

相反,如果我在没有分页查询的情况下运行查询:

select owner, object_name, object_id
from large_t
order by object_id

那么所需的排序区域为37M,证明两个查询中的排序大致相同。

通常,如果您想有效地返回排序查询的 TOP N,您将需要排序列上的索引 - 这将完全阻止 Oracle 需要排序。因此,我在 OBJECT_ID 上创建了一个索引,然后再次解释了这两个查询。

这次第一次查询使用了索引并在 0.2 秒内返回,而第二次查询没有使用新索引,速度慢很多。

所以我从这个快速的分析中得出的结论是,在一般情况下,使用 rownum 进行过滤或分析 row_number 函数的性能大致相同。但是,rownum 示例使用我在 table 上创建的索引自动启动,而 row_number 没有。也许我可以让它通过一些提示来使用索引 - 这是你可以尝试的其他东西。

于 2011-08-16T10:43:24.413 回答
4

除了答案中提到的其他差异外,您还应该考虑性能。这里有一份非权威但很有趣的报道,比较了各种分页方式,其中使用了ROWNUMcompare to ROW_NUMBER() OVER()

https://web.archive.org/web/20160901191310/http://www.inf.unideb.hu:80/~gabora/pagination/results.html

于 2012-07-18T09:48:41.427 回答
0

要生成您自己的经验结果:

-- Create test table
CREATE TABLE test_large_tab (
  tlt_id   NUMBER,
  tlt_data VARCHAR2(50)
);

-- Load with data
BEGIN
   FORALL i IN 1 .. 1000000
      INSERT INTO test_large_tab
      (
       tlt_id,
       tlt_data
      )
      VALUES
      (
       i,
       TO_CHAR(sysdate-i, 'FMMon ddth, YYYY')
      );
END;

当然,您可以增加表格的大小以适应您的测试目的!

设置时间并对大表运行两个查询。

更改表结构以更好地适应您的测试,因为您可能希望某些列也为您的查询建立索引等,但本质上它是一个简单的测试,不会花很长时间运行。

如果两者出现的时间大致相同,则使用最易读(因此可支持)的版本。

于 2011-08-16T09:59:01.093 回答