• 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
    ID CATEGORY_NAME LIST_ORDER ROWNO
    23794 fdfdf 0 1
    22899 上装1 0 2
    5260 薯片 0 3
    5094 厨房家电 0 4
    23029 凉血止血 0 5
    2.SELECT t.*, ROWNUM AS rowno FROM (select * from table ORDER BY LIST_ORDER) t WHERE ROWNUM<11
    ID CATEGORY_NAME LIST_ORDER ROWNO
    23794 fdfdf 0 1
    23204 子目录222-22 0 2
    23203 子目录222-21 0 3
    23202 子目录222-20 0 4
    23200 子目录222-18 0 5
    23198 子目录222-16 0 6
    22899 上装1 0 7
    5260 薯片 0 8
    5094 厨房家电 0 9
    23029 凉血止血 0 10

    结果很明显:

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

           于是这样在第三步,我们取第#startRow#个数据后的所有数据时,就会一直把最后面的“凉血止血”类似的数据给取出来,导致出现重复的错误,并且前面的数据会有取不到的可能性。那么为什么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生成不一致

    解决办法:
    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可能会很慢。

     

  • 相关阅读:
    javascript 之 面向对象【继承】
    javascript 之 面向对象【创建对象】
    javascript 之 函数
    EffectiveJava(26)使用泛型类替代普通类
    JAVA加解密 -- 数字签名算法
    JAVA加解密 -- 对称加密算法与非对称加密算法
    EffectiveJava(25)泛型和数组的使用取舍及规范
    EffectiveJava(24)使用@SuppressWarnings("unchecked")消除非受检警告
    EffectiveJava(23)为什么不能在新生代码中使用原生态类型
    JAVA加解密 -- 消息摘要算法
  • 原文地址:https://www.cnblogs.com/lwlxqlccc/p/8676045.html
Copyright © 2020-2023  润新知