Oracle 数据库分页查询的几种写法

简介: 包含Oracle 12c 以后的新语法

     从Oracle 12C开始,提供了一种行限制(row limit)的写法,这种写法也可以用于分页查询。在介绍新写法之前,先介绍一下Oracle 12C之前的分页查询的写法,分析一下各种写法的性能差异。

先介绍一下Oracle 12C中行限制的新写法,顺便看一下本文中用作演示的示例数据,emp表的数据总共有100万行。

select*from emp orderby sal fetch first 5 rows only;            EMPNO ENAME                                                     MGR HIREDATE         SAL        COM     DEPTNO
---------- -------------------------------------------------- ---------- --------- ---------- ---------- ----------92  Bryana  Worswick                                          8003-JUL-18250411099  Rufus  McDermott-Row                                      6301-MAR-022512611036  Jacenta  Joust                                            7131-JAN-0525159302  Blisse  Adamoli                                           6512-APR-142517672098  Simona  Shropshire                                        5216-AUG-0825182488

1 使用子查询实现分页

1.1 不需要排序

select a.EMPNO,ENAME,SAL 
from(select empno,ename,sal,rownum rowno from emp  )             a where a.rowno>=11and  a.rowno<=15;             EMPNO ENAME                                                     SAL
---------- -------------------------------------------------- ----------75  Shell  Whatman                                          505952  Elisabet  Gristock                                      441911  Dag  Quakley                                            43036  Zebedee  Pesselt                                        46184  Anthe  Noar                                             3198

上面的写法返回的结果是正确的,但是性能较差,看一下它的执行计划

----------------------------------------------------------------------------| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)|Time|----------------------------------------------------------------------------|0|SELECT STATEMENT    ||1000K|62M|4956(1)|00:00:01||*1|  VIEW               ||1000K|62M|4956(1)|00:00:01||2|COUNT|||||||3|TABLE ACCESS FULL| EMP  |1000K|55M|4956(1)|00:00:01|----------------------------------------------------------------------------         Predicate Information (identified by operation id):---------------------------------------------------1- filter("A"."ROWNO"<=15AND"A"."ROWNO">=11)

从上面的执行计划可以看出,在查询时做了全表扫面,总共扫描了1百万行,上面的语句可以优化一下,改成下面的写法

select a.EMPNO,ENAME,SAL 
from(select empno,ename,sal,rownum rowno
from emp where rownum <=15) a
where a.rowno>=11;

上面的语句把条件查询的上限放到了子查询里面,执行计划也发生了变化

--------------------------------------------------------------------------| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)|Time|----------------------------------------------------------------------------|0|SELECT STATEMENT    ||15|990|2(0)|00:00:01||*1|  VIEW               ||15|990|2(0)|00:00:01||*2|COUNT STOPKEY     |||||||3|TABLE ACCESS FULL| EMP  |15|870|2(0)|00:00:01|----------------------------------------------------------------------------     Predicate Information (identified by operation id):---------------------------------------------------1- filter("A"."ROWNO">=11)2- filter(ROWNUM<=15)

     语句的执行计划发生了改变,执行计划中第二个操作是COUNT STOPKEY操作,Oracle在扫描完满足过滤条件的行之后结束了全表扫描,总共只扫描了5数据,执行的性能比前一条语句好了不少。当然,随着向后翻页,语句的性能会逐渐变差,比如翻页到900011行时的执行计划

----------------------------------------------------------------------------| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)|Time|----------------------------------------------------------------------------|0|SELECT STATEMENT    ||900K|56M|4461(1)|00:00:01||*1|  VIEW               ||900K|56M|4461(1)|00:00:01||*2|COUNT STOPKEY     |||||||3|TABLE ACCESS FULL| EMP  |900K|49M|4461(1)|00:00:01|----------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------1- filter("A"."ROWNO">=900011)2- filter(ROWNUM<=900015)

     总共扫描了90万行,不过还是比前面一种写法少扫了10万行,也就是说,这种写法的性能最差时同前面一种写法相同,大多数情况下要比前一种写法性能好很多。

1.2 需要排序

     如果分析查询的结果是需要经过排序的,使用子查询来实现分页就要复杂一点,下面的写法是错误的,返回的是错误的结果

select a.EMPNO,ENAME,SAL
from(select empno,ename,sal,rownum rowno from emp  orderby SAL) a
where a.rowno>=11and  a.rowno<=15;             EMPNO ENAME                                                   SAL
---------- -------------------------------------------------- ----------4  Anthe  Noar                                             319811  Dag  Quakley                                            430352  Elisabet  Gristock                                      44196  Zebedee  Pesselt                                        461875  Shell  Whatman                                          5059

这种写法错误的原因是取得的行号实际是对emp按照SAL列排序之前的数据,根据这个错误的行号取得的数据自然也是错误的,要想获得正确的行号,就需要加一层子查询,将语句写成下面这样

select b.EMPNO,b.ENAME,b.SALfrom(select rownum ROWNO, a.*from(select empno,ename,sal from emp  orderby SAL) a
where rownum<=15) b where b.rowno>=11;                 EMPNO ENAME                                                     SAL
---------- -------------------------------------------------- ----------71  Meridith  Stanbrooke                                    252661  Vin  Tuft                                               252614  Alyss  Concannon                                        25269  Rocky  Ravens                                           252698  Madelyn  Norewood                                       2526

    这时,语句的实际执行计划如下所示

-------------------------------------------------------------------------------------------------------------------| Id  | Operation             | Name | Starts | E-Rows | A-Rows |   A-Time| Buffers |  OMem |1Mem | Used-Mem |-------------------------------------------------------------------------------------------------------------------|0|SELECT STATEMENT      ||1||5|00:00:00.21|18268|||||*1|  VIEW                 ||1|1000K|5|00:00:00.21|18268|||||2|COUNT||1||1000K|00:00:00.82|18268|||||3|    VIEW               ||1|1000K|1000K|00:00:00.80|18268|||||4|     SORT ORDERBY||1|1000K|1000K|00:00:00.79|18268|84M|3151K|74M (0)||5|TABLE ACCESS FULL| EMP  |1|1000K|1000K|00:00:00.04|18268||||Predicate Information (identified by operation id):---------------------------------------------------1- filter(("B"."ROWNO"<=15AND"B"."ROWNO">=11))

     从上面的执行计划来看,Oracle在执行查询转换时未能将查询条件下推到排序操作,对全表扫描到的100万行数据进行了排序,这时因为Oracle只能将条件下推到下一层的子查询,不能向下推到第二层的子查询当中。将语句改一下,将查询页面的上限写到子查询b内

select b.EMPNO,b.ENAME,b.SALfrom(select rownum ROWNO, a.*from(select empno,ename,sal from emp  orderby SAL) a 
where rownum<=15) b 
where b.rowno>=11;

改写后的语句执行结果时正确的,查询的结果同改写前相同,看一下这个语句的实际执行计划

----------------------------------------------------------------------------------------------------------------------| Id  | Operation                | Name | Starts | E-Rows | A-Rows |   A-Time| Buffers |  OMem |1Mem | Used-Mem |----------------------------------------------------------------------------------------------------------------------|0|SELECT STATEMENT         ||1||5|00:00:00.13|18267|||||*1|  VIEW                    ||1|15|5|00:00:00.13|18267|||||*2|COUNT STOPKEY          ||1||15|00:00:00.13|18267|||||3|    VIEW                  ||1|1000K|15|00:00:00.13|18267|||||*4|     SORT ORDERBY STOPKEY||1|1000K|15|00:00:00.13|18267|2048|2048|2048(0)||5|TABLE ACCESS FULL   | EMP  |1|1000K|1000K|00:00:00.04|18267||||    Predicate Information (identified by operation id):---------------------------------------------------1- filter("B"."ROWNO">=11)2- filter(ROWNUM<=15)4- filter(ROWNUM<=15)

     从语句的实际执行计划来看,虽然Oracle优化器在评估时排序操作返回的数据是1百万行,但是实际只排序了15行就结束了排序操作,语句执行的时间和改写之前相比减少了很多,使用的内存和改写之前相比也减少了很多。

2 使用分析函数实现分页查询

    上面的有序分析查询的语句用了2层子查询嵌套,看起来比较复杂,有没有看起来比较简单一点的写法?看起来简单的写法还是有的,用分析函数也可以实现有序分页查询,其实现的原理是先用分析函数排序,取得排序后的行号,根据行号查询所需要的页。上面的语句可以用分析函数改写一下:

select EMPNO,ENAME,SAL from(select row_number()  over(orderby sal) rownumber, e.*from emp e)where rownumber >=11and rownumber <=15;

改写后的语句的执行结果也是正确的,实际执行计划如下

----------------------------------------------------------------------------------------------------------------------| Id  | Operation                | Name | Starts | E-Rows | A-Rows |   A-Time| Buffers |  OMem |1Mem | Used-Mem |----------------------------------------------------------------------------------------------------------------------|0|SELECT STATEMENT         ||1||5|00:00:00.15|18267|||||*1|  VIEW                    ||1|15|5|00:00:00.15|18267|||||*2|   WINDOW SORT PUSHED RANK||1|1000K|15|00:00:00.15|18267|2048|2048|2048(0)||3|TABLE ACCESS FULL     | EMP  |1|1000K|1000K|00:00:00.04|18267||||----------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------1- filter(("ROWNUMBER">=11AND"ROWNUMBER"<=15))2- filter(ROW_NUMBER() OVER (ORDERBY"SAL")<=15)

     从这条语句的实际执行计划来看,WINDOW SORT PUSHED RANK也是执行了15行之后就停止了操作,由于减少了子查询,实际的执行事件比子查询时还要少些,性能要稍好一点。

3 使用offset实现分页查询

     上面的两种写法,一种使用了2层子查询,一种使用了分析函数,2层子查询看起来比较复杂,分析函数理解起来也有一定的难度,有没有写起来简单,理解起来也比较容易的写法,就像MySQL那样。Oracle 从12C之后为select 语句提供了offset fetch的写法,也可以用来分页查询

3.1 无序查询

用offset实现无序的分页查询,语句如下:

select EMPNO,ENAME,SAL from emp  offset 10 rows fetch next 5 rows only;

这条语句很容易理解,offset设置的偏移量,fecth next是取偏移量之后的5条数据,语句的执行结果是正确的,执行计划如下:

-------------------------------------------------------------------------------| Id  | Operation              | Name | Rows  | Bytes | Cost (%CPU)|Time|-------------------------------------------------------------------------------|0|SELECT STATEMENT       ||15|990|2(0)|00:00:01||*1|  VIEW                  ||15|990|2(0)|00:00:01||*2|   WINDOW NOSORT STOPKEY||15|870|2(0)|00:00:01||3|TABLE ACCESS FULL   | EMP  |15|870|2(0)|00:00:01|-------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------1- filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=15AND"from$_subquery$_002"."rowlimit_$$_rownumber">10)2- filter(ROW_NUMBER() OVER (ORDERBYNULL)<=15)

     这条语句无需看实际执行计划,就可以看出来只扫描了15行数据,从WINDOW NOSORT STOPKEY来看,扫描完了符合条件的数据之后即刻停止全表扫描。

3.2 有序查询

有序查询写法也非常简单,在语句中加入排序即可

select EMPNO,ENAME,SAL from emp orderby sal offset 10 rows fetch next 5 rows only;

上面的语句执行结果是正确的,执行计划如下:

----------------------------------------------------------------------------------------------------------------------| Id  | Operation                | Name | Starts | E-Rows | A-Rows |   A-Time| Buffers |  OMem |1Mem | Used-Mem |----------------------------------------------------------------------------------------------------------------------|0|SELECT STATEMENT         ||1||5|00:00:00.15|18267|||||*1|  VIEW                    ||1|15|5|00:00:00.15|18267|||||*2|   WINDOW SORT PUSHED RANK||1|1000K|15|00:00:00.15|18267|2048|2048|2048(0)||3|TABLE ACCESS FULL     | EMP  |1|1000K|1000K|00:00:00.23|18267||||----------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------1- filter(("from$_subquery$_002"."rowlimit_$$_rownumber"<=15AND"from$_subquery$_002"."rowlimit_$$_rownumber">10))2- filter(ROW_NUMBER() OVER (ORDERBY"SAL")<=15)

这个执行计划看起来有点眼熟?没错,这个执行计划同使用分析函数的执行计划相同。但是语句看起来简单多了,也很容易理解。

4 分页查询新写法的原理

从执行计划来看,Oracle 12C之后的新写法也是利用了分析函数来实现的,具体是如何实现的可以利用10053事件从语句执行计划生成的过程中看到。这里不再展示如何开启10053事件,只是将语句经过查询转后的最终结果截取出来

SELECT"from$_subquery$_002"."EMPNO""EMPNO","from$_subquery$_002"."ENAME""ENAME","from$_subquery$_002"."SAL""SAL"FROM(SELECT"EMP"."EMPNO""EMPNO","EMP"."ENAME""ENAME","EMP"."SAL""SAL","EMP"."SAL""rowlimit_$_0",                ROW_NUMBER() OVER (ORDERBY"EMP"."SAL")"rowlimit_$$_rownumber"FROM"TEST"."EMP""EMP"WHERE10<15)"from$_subquery$_002"WHERE"from$_subquery$_002"."rowlimit_$$_rownumber"<=15AND"from$_subquery$_002"."rowlimit_$$_rownumber">10ORDERBY"from$_subquery$_002"."rowlimit_$_0"

经过优化器转换后,语句看起来比之前复杂多了,但是可以看到,转换后的语句使用分析函数,除了名字有些差异之外,同之前的用分析函数实现排序分页查询的语句相同。Oracle用另一种语法进行了包装,使我们写起来更容易,也更好理解。

相关文章
|
3月前
|
Oracle 关系型数据库 Linux
【赵渝强老师】Oracle数据库配置助手:DBCA
Oracle数据库配置助手(DBCA)是用于创建和配置Oracle数据库的工具,支持图形界面和静默执行模式。本文介绍了使用DBCA在Linux环境下创建数据库的完整步骤,包括选择数据库操作类型、配置存储与网络选项、设置管理密码等,并提供了界面截图与视频讲解,帮助用户快速掌握数据库创建流程。
369 93
|
2月前
|
Oracle 关系型数据库 Linux
【赵渝强老师】使用NetManager创建Oracle数据库的监听器
Oracle NetManager是数据库网络配置工具,用于创建监听器、配置服务命名与网络连接,支持多数据库共享监听,确保客户端与服务器通信顺畅。
206 0
|
5月前
|
存储 Oracle 关系型数据库
服务器数据恢复—光纤存储上oracle数据库数据恢复案例
一台光纤服务器存储上有16块FC硬盘,上层部署了Oracle数据库。服务器存储前面板2个硬盘指示灯显示异常,存储映射到linux操作系统上的卷挂载不上,业务中断。 通过storage manager查看存储状态,发现逻辑卷状态失败。再查看物理磁盘状态,发现其中一块盘报告“警告”,硬盘指示灯显示异常的2块盘报告“失败”。 将当前存储的完整日志状态备份下来,解析备份出来的存储日志并获得了关于逻辑卷结构的部分信息。
|
3月前
|
SQL Oracle 关系型数据库
Oracle数据库创建表空间和索引的SQL语法示例
以上SQL语法提供了一种标准方式去组织Oracle数据库内部结构,并且通过合理使用可以显著改善查询速度及整体性能。需要注意,在实际应用过程当中应该根据具体业务需求、系统资源状况以及预期目标去合理规划并调整参数设置以达到最佳效果。
312 8
|
5月前
|
SQL Oracle 关系型数据库
比较MySQL和Oracle数据库系统,特别是在进行分页查询的方法上的不同
两者的性能差异将取决于数据量大小、索引优化、查询设计以及具体版本的数据库服务器。考虑硬件资源、数据库设计和具体需求对于实现优化的分页查询至关重要。开发者和数据库管理员需要根据自身使用的具体数据库系统版本和环境,选择最合适的分页机制,并进行必要的性能调优来满足应用需求。
276 11
|
5月前
|
Oracle 关系型数据库 数据库
数据库数据恢复—服务器异常断电导致Oracle数据库报错的数据恢复案例
Oracle数据库故障: 某公司一台服务器上部署Oracle数据库。服务器意外断电导致数据库报错,报错内容为“system01.dbf需要更多的恢复来保持一致性”。该Oracle数据库没有备份,仅有一些断断续续的归档日志。 Oracle数据库恢复流程: 1、检测数据库故障情况; 2、尝试挂起并修复数据库; 3、解析数据库文件; 4、导出并验证恢复的数据库文件。
|
5月前
|
存储 Oracle 关系型数据库
【赵渝强老师】Oracle RMAN的目录数据库
Oracle RMAN默认将备份元信息存储在控制文件中,但控制文件损坏或丢失会导致恢复失败,且备份增多会使控制文件无限增长。为解决这些问题,Oracle引入了RMAN目录数据库(Catalog Database),专门用于存储RMAN备份的元信息。使用目录数据库可提升备份管理效率,支持多数据库共享、长期备份历史记录存储,并可保存RMAN脚本。本文详细介绍了如何创建目录数据库、注册目标数据库及其操作步骤。
145 0
|
移动开发 Oracle 关系型数据库
|
SQL 移动开发 Oracle
Oracle分页查询语句(三)
Oracle的分页查询语句基本上可以按照本文给出的格式来进行套用。 Oracle分页查询语句(一):http://yangtingkun.itpub.net/post/468/100278 Oracle分页查询语句(二):http://yangtingkun.itpub.net/post/468/101703 继续看查询的第二种情况,包含表连接的情况: SQL> CREATE TABLE T AS SELECT * FROM DBA_USERS; 表已创建。
826 0
|
SQL 移动开发 Oracle
Oracle分页查询语句(四)
Oracle的分页查询语句基本上可以按照本文给出的格式来进行套用。 Oracle分页查询语句(一):http://yangtingkun.itpub.net/post/468/100278 Oracle分页查询语句(二):http://yangtingkun.
1077 0

热门文章

最新文章

推荐镜像

更多