10

我有两个我考虑使用的分页查询。

第一个是

SELECT * FROM ( SELECT rownum rnum, a.* from (
    select * from members
) a WHERE rownum <= #paging.endRow# ) where rnum > #paging.startRow#

第二个是

SELECT * FROM ( SELECT rownum rnum, a.* from (
    select * from members
) a ) WHERE rnum BETWEEN #paging.startRow# AND #paging.endRow#

您如何看待哪个查询更快?

4

3 回答 3

15

我现在实际上没有 Oracle 的可用性,但是用于分页的最佳 SQL 查询肯定如下

select *
from (
        select rownum as rn, a.*
        from (
                select *
                from my_table
                order by ....a_unique_criteria...
            ) a
    )
where rownum <= :size
    and rn >  (:page-1)*:size

http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html

为了实现一致的分页,您应该使用唯一的条件对行进行排序,这样做可以避免为页面 X 加载您已经为页面 Y (!=X) 加载的行。

编辑:

1) 使用唯一标准对行进行排序意味着以每行在每次执行查询时保持相同位置的方式对数据进行排序

2) 包含 ORDER BY 子句中使用的所有表达式的索引将有助于更快地获得结果,尤其是第一页。使用该索引,优化器选择的执行计划不需要对行进行排序,因为它将返回按其自然顺序滚动索引的行。

3) 顺便说一下,从查询中分页结果的最快方法是只执行一次查询并处理来自应用程序端的所有流程。

于 2012-07-27T17:35:27.977 回答
9

看一下执行计划,例如 1000 行:

SELECT *
  FROM (SELECT ROWNUM rnum
              ,a.*
          FROM (SELECT *
                  FROM members) a
         WHERE ROWNUM <= endrow#)
 WHERE rnum > startrow#;

--------------------------------------------------------------------------------
| Id  | Operation            | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |         |  1000 | 39000 |     3   (0)| 00:00:01 |
|*  1 |  VIEW                |         |  1000 | 39000 |     3   (0)| 00:00:01 |
|   2 |   COUNT              |         |       |       |            |          |
|*  3 |    FILTER            |         |       |       |            |          |
|   4 |     TABLE ACCESS FULL| MEMBERS |  1000 | 26000 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RNUM">"STARTROW#")
   3 - filter("MEMBERS"."ENDROW#">=ROWNUM)

和 2。

SELECT *
  FROM (SELECT ROWNUM rnum
              ,a.*
          FROM (SELECT *
                  FROM members) a)
 WHERE rnum BETWEEN startrow# AND endrow#;

-------------------------------------------------------------------------------
| Id  | Operation           | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |         |  1000 | 39000 |     3   (0)| 00:00:01 |
|*  1 |  VIEW               |         |  1000 | 39000 |     3   (0)| 00:00:01 |
|   2 |   COUNT             |         |       |       |            |          |
|   3 |    TABLE ACCESS FULL| MEMBERS |  1000 | 26000 |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RNUM"<="ENDROW#" AND "RNUM">="STARTROW#")

除此之外,我会说版本 2可能会稍微快一些,因为它少了一步。但是我不知道您的索引和数据分布情况,因此您可以自己获取这些执行计划并判断数据的情况。或者简单地测试一下。

于 2012-07-27T05:39:36.627 回答
0

A已经在这里回答了但是让我复制粘贴。

只是想总结一下答案和评论。有很多方法可以进行分页。

在 oracle 12c 之前,没有 OFFSET/FETCH 功能,因此请按照@jasonk 的建议查看白皮书。这是我找到的关于不同方法的最完整的文章,并详细解释了优缺点。在这里复制粘贴它们需要大量时间,所以我想这样做。

jooq 创建者还发表了一篇很好的文章,解释了 Oracle 和其他数据库分页的一些常见警告。jooq 的博文

好消息,从 oracle 12c 开始,我们有了新的 OFFSET/FETCH 功能。OracleMagazine 12c 新特性。请参考《Top-N 查询与分页》

您可以通过发出以下语句来检查您的 oracle 版本

SELECT * FROM V$VERSION
于 2015-11-03T11:37:24.193 回答