ORACLE中order by造成分页不正确原因分析

           这些天工作中在为调用方提供一个分页接口时,调用方一直反应有部分数据取不到,且取到的数据有重复的内容。


于是我按以下步骤排查了下错误。


           1.检查分页页码生成规则是否正确。


           2.检查SQL语句是否正确。(后来确认是SQL中order by作祟,犯了想当然的错误,认为SQL是最不可能出问题的地方,因为分页SQL格式与老代码分页SQL格式一样,所以没有怀疑。)

         

           3.检查调用方入参是否正确。


           4.检查调用方循环遍历边界。


           5.在上述步骤验证没问题后,怀疑ibatis,调试到ibatis中,花费大量时间。


           6.再次验证SQL,发现问题。


           经过这么多步骤,发现自己考虑问题都想复杂了,最简单的错误原因往往就是其错误原因,那么我们就来分析为什么 order by 会造成分页SQL出错。


分页SQL:

SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM (
            select * from table
                         ORDER BY LIST_ORDER
) t WHERE ROWNUM<#endRow# ) WHERE rowno>=#startRow#


看似这个SQL没有什么问题,


执行过程:

select * from table
                         ORDER BY LIST_ORDER

1.首先取出table表的所有数据,并按照list_order排序,其中list_order可以取0,1,2,3,4,5这六个数


SELECT t.*, ROWNUM AS rowno FROM (
.....) t WHERE ROWNUM<#endRow#


2.取出table表中前#endRow#个数据。


SELECT * FROM (

......) WHERE rowno>=#startRow#


3.取出从第#startRow#个数据后的所有数据。


于是这样就取出了table中#startRow#到#endRow#的所有数据,可是我们忽略了这个问题,ROWNUM是不变的吗?答案是order by 会导致 rownum发生变化。


我们来验证一下 比较两个SQL 的结果。


1.SELECT t.*, ROWNUM AS rowno FROM (
            select * from table
                         ORDER BY LIST_ORDER
) t WHERE ROWNUM<6

IDCATEGORY_NAMELIST_ORDERROWNO
23794fdfdf01
22899上装102
5260薯片03
5094厨房家电04
23029凉血止血05



2.SELECT t.*, ROWNUM AS rowno FROM (
            select * from table
                         ORDER BY LIST_ORDER
) t WHERE ROWNUM<11

IDCATEGORY_NAMELIST_ORDERROWNO
23794fdfdf01
23204子目录222-2202
23203子目录222-2103
23202子目录222-2004
23200子目录222-1805
23198子目录222-1606
22899上装107
5260薯片08
5094厨房家电09
23029凉血止血010


结果很明显,以“凉血止血”为例,在第一个SQL中,“凉血止血”的rownum为5, 而在第二个SQL中“凉血止血”的rownum为10,他的rownum 发生了变化,


于是这样在第三步,我们取第#startRow#个数据后的所有数据时,就会一直把最后面的“凉血止血”类似的数据给取出来,导致出现重复的错误,并且前

面的数据会有取不到的可能性。


那么为什么rownum会发生变化呢?


首先我们来看rownum的定义:

对于rownum来说它是oracle系统顺序分配为从查询返回的行的编号,返回的第一行分配的是1,第二行是2,依此类推,这个伪字段可以用于

限制查询返回的总行数,且rownum不能以任何表的名称作为前缀。


听起来很绕口对吧,其实简单的说就是,你去查数据库,rownum就是oracle根据返回数据的顺序给他的一个编号,谁先返回谁就是1,如果不存在order

by排序条件那么它就是oracle的存储顺序。


于是当本文中取出的数据的list_order这个字段的值是一样的时候,oracle在返回数据时,返回数据顺序不是固定的,我们取前5个数据的时候,数据库返

回数据的顺序,与我们取前11个数据时,数据库返回数据的顺序是完全不同的,于是他生成的rownum伪列的编号就完全不一样,就导致了这样的错误。


那么造成这种错误前提:


1.order by 排序字段不唯一


2.分页使用的是类似以下SQL的结构

SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM (
            select * from table
                         ORDER BY LIST_ORDER
) t WHERE ROWNUM<#endRow# ) WHERE rowno>=#startRow#

 

3.数据库的数据足够多,这样才比较容易发生rownum生成不一致


4.使用oracle数据库


各位同学们可以看看是否自己的代码中有类似问题哦。


解决办法:


1.提取rownum到外部:

SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM (
            select * from table
                         ORDER BY LIST_ORDER
) t  ) WHERE rowno>=#startRow# AND ROWNUM<#endRow#

优点:适用各种order by不同字段,因为内部取值SQL是不变的,所以取值顺序是不变的,分页肯定不会出错

缺点:SQL效率变低,每次都相当于取出了所有的数据,然后再进行遍历比较,依赖于oracle的存储顺序,当oracle存储顺序发生变化时,需要注意。(当然那时候很多类型的SQL都要注意了,呵呵)


2.order by后面加上唯一性字段(类似主键id) :

SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM (
            select * from table
                         ORDER BY LIST_ORDER,id
) t  ) WHERE rowno>=#startRow# AND ROWNUM<#endRow#

优点:修改简单,原来的代码不用做过多更改

缺点:sql效率有可能会比第一种修改方式更加低,因为在根据list_order排序后,还要根据id再排一次序,当数据量比较多时,SQL可能会很慢。


  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值