在目前很多系统中,界面数据分页(Page)已经成为一项通用功能。基本上,每种框架、结构和对应的数据库,都有一些成熟的现成解决方案。在Oracle中,rownum伪列是初学者非常容易用错的功能。本篇就介绍一个实际的分页和rownum结合使用时候的故障案例。
(Oracle)Rownum释疑
Oracle数据库学习之Rownum
Oracle 中Rownum用法总结,Rownum 与 ROWID 区别
Oracle利用Rownum和rowid分页
Oracle数据库中Rownum分页
在CentOS 6.4下安装Oracle 11gR2(x64)
Oracle 11gR2 在VMWare虚拟机中安装步骤
Debian 下 安装 Oracle 11g XE R2
Oracle 11gR2 在VMWare虚拟机中安装步骤
1、问题简述
系统后台数据库采用Oracle 11gR2版本。
SQL> select * from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE11.2.0.3.0Production
TNS for IBM/AIX RISC System/6000: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production
数据表inputfile_log记录数据文件日志信息。在界面上提供数据查询、操作和排序。界面提供所有字段的查询排序功能。数据表主键为inputfile_log_seq,一个字段为ISO_CODE。
在测试阶段,测试人员不时报出某些特定字段在排序过程中有一些问题,排序之后,跳转到下一页数据取值不变。但是,在架构层面,没有明显的故障和Bug存在,前端故障也大都是偶发性的。
一个偶然的机会,测试和开发人员定位到了问题的所在:对其他字段进行排序处理之后,分页功能一般不会有故障。只有在ISO_CODE进行排序,之后进行跳转分页的时候,才会有问题,而且问题也是偶发性,不具有必然性。
经过定位,发现页面前端输出的SQL语句有一些问题。页面前端采用Hibernate实体映射类。分页采用数据库“真分页”技术——数据库只把要显示的数据传递到前端。Hibernate负责SQL语句的生成执行。
定位的SQL语句为:
select *
from (select a1.inputfile_log_seq, a1.ISO_CODE,rownum rwn
from (select * from inputfile_log t order by t.ISO_CODE) a1
where rownum 45;
Oracle中分页的语句方案很多,这种利用rownum取段的方法也是普遍接受的方法。问题在哪儿呢?下面我们执行语句看结果。
SQL> select *
2from (select a1.inputfile_log_seq, a1.ISO_CODE,rownum rwn
3from (select * from inputfile_log t order by t.ISO_CODE) a1
4where rownum 45;
INPUTFILE_LOG_SEQ ISO_CODERWN
----------------- -------- ----------
1901 AU46
1903 AU47
1906 AU48
1142 AU49
1157 AU50
SQL> select *
2from (select a1.inputfile_log_seq, a1.ISO_CODE, rownum rwn
3from (select * from inputfile_log t order by t.ISO_CODE) a1
4where rownum 50;
INPUTFILE_LOG_SEQ ISO_CODERWN
----------------- -------- ----------
1901 AU51
1903 AU52
1906 AU53
1142 AU54
1157 AU55
看出问题了,在SQL语句中,我们希望看到连续10条记录。但是虽然替换了数据段范围,但是结果集却是相同的。
反映到页面上,就是按照ISO_CODE进行排序之后,分页功能失效,点击跳到下一页,但是显示的数据却没有变化。
2、分析和实验
看似很诡异的问题,难道是Hibernate语句方案有问题?替换为其他的排序字段,问题似乎不存在。
SQL> select *
2from (select a1.inputfile_log_seq, a1.ISO_CODE,rownum rwn
3from (select * from inputfile_log t order by t.inputfile_log_seq) a1
4where rownum 50;
INPUTFILE_LOG_SEQ ISO_CODERWN
----------------- -------- ----------
997 AU51
998 AU52
999 AU53
1000 AU54
1001 AU55
SQL>
SQL> select *
2from (select a1.inputfile_log_seq, a1.ISO_CODE,rownum rwn
3from (select * from inputfile_log t order by t.inputfile_log_seq) a1
4where rownum 45;
INPUTFILE_LOG_SEQ ISO_CODERWN
----------------- -------- ----------
992 AU46
993 AU47
994 AU48
995 AU49
996 AU50
排序结果正常。看来问题还是出在ISO_CODE上。
条条大路通罗马,换一种方法,试试结果如何呢?Oracle中还有很多其他的分页方案。
SQL> select *
2from (select a1.inputfile_log_seq, a1.ISO_CODE,rownum rwn
3from (select * from inputfile_log t order by t.ISO_CODE) a1)
4where rwn > 45
5and rwn
SQL> select *
2from (select a1.inputfile_log_seq, a1.ISO_CODE, rownum rwn
3from (select * from inputfile_log t order by t.ISO_CODE) a1)
4where rwn > 50
5and rwn