工作中遇到的问题:

为调用方提供一个分页接口时,调用方一直反应有部分数据取不到,且取到的数据有重复的内容,于是我按以下步骤排查了下错误。

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可能会很慢。

 

ORACLE中order by造成分页不正确原因分析的更多相关文章

  1. oracle中order by造成分页错误

    问题:今天在工作中,在service中调用分页查询列表接口的时候,返回的到页面的数据中总是存在缺失的数据,还有重复的数据. 分析:select * from (select ROWNUM rn,t.* ...

  2. Oracle中获取执行计划的几种方法分析

    以下是对Oracle中获取执行计划的几种方法进行了详细的分析介绍,需要的朋友可以参考下     1. 预估执行计划 - Explain PlanExplain plan以SQL语句作为输入,得到这条S ...

  3. Oracle中修改sysman和dbsnmp密码正确流程

    1.停止dbconsole $ emctl stop dbconsole 查看状态,确认dbconsole已经停止 $ emctl status dbconsole   2.修改sysman用户和db ...

  4. 用oracle中的Row_Number实现分页

    Row_Number实现分页   1:首先是 select ROW_NUMBER() over(order by id asc) as 'rowNumber', * from table1 生成带序号 ...

  5. Oracle中order by case 用法

    select * from ly_familyinformation ' ' order by case when relation = '购房人/申请人' then when relation = ...

  6. 查看Oracle中存储过程长时间被卡住的原因

    1:查V$DB_OBJECT_CACHE SELECT * FROM V$DB_OBJECT_CACHE WHERE name='CUX_OE_ORDER_RPT_PKG' AND LOCKS!='0 ...

  7. 转://Oracle中定义者权限和调用者权限案例分析

    定义者权限:定义者权限指使用它所有者的权限,而不是当前用户来执行过程.因此,你可以限制用户执行的数据库操作,允许他们仅通过运行定义者权限的过程和函数访问数据.创建过程.函数和程序包的默认权限是定义者权 ...

  8. oracle中union和union all区别与性能分析

    [ 概要 ] 经常写sql的同学可能会用到union和union all这两个关键词, 可能你知道使用它们可以将两个查询的结果集进行合并, 那么二者有什么区别呢? 下面我们就简单的分析下. [ 比较 ...

  9. 本机未装Oracle数据库时Navicat for Oracle 报错:Cannot create oci environment 原因分析及解决方案

    因为要更新数据库加个表,远程桌面又无法连接...所以就远程到另外一台电脑,然后用navicat通过内网修改目标数据库. 一直用着navicat操作数据库,所以很速度的弄好然后新建连接进入数据库. 然而 ...

随机推荐

  1. Dll注入:修改PE文件 IAT注入

    PE原理就不阐述了, 这个注入是PE感染的一种,通过添加一个新节注入,会改变PE文件的大小,将原有的导入表复制到新节中,并添加自己的导入表描述符,最后将数据目录项中指向的导入表的入口指向新节. 步骤: ...

  2. May 13th 2017 Week 19th Saturday

    Mountains look beautiful from a distance. 远处看山山更美. This gnomic seems to circulate very long, its mor ...

  3. April 1 2017 Week 13 Saturday

    There is more to life than increasing its speed. 生活不仅仅是匆匆赶路. Get a life, a real life, not a manic pu ...

  4. (转)写的非常好的一篇HTTP协议详解

    原文地址:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html#3931004(推荐看原文,因为原文文章末有更详细链接) 当今 ...

  5. Uva 11922 Splay

    Splay(伸展树)实现可分裂与合并的序列 对于BST,除了Treap树之外,还有一种Splay的伸展树,他能快速的分裂与合并. 重要的操作是伸展操作,将一个指定的结点 x 旋转到根的过程. 分三种情 ...

  6. POJ 2155 Matrix【二维树状数组+YY(区间计数)】

    题目链接:http://poj.org/problem?id=2155 Matrix Time Limit: 3000MS   Memory Limit: 65536K Total Submissio ...

  7. print (re.findall("(?:abc)+","abcabcabc"))

    _*_ coding:utf-8 _*_ import re findall 有括号优先级,所以我们这里一直出现的都是 abc print (re.findall("(abc)+" ...

  8. JavaScript内存管理

    低级语言,比如C,有低级的内存管理基元,想malloc(),free().另一方面,JavaScript的内存基元在变量(对象,字符串等等)创建时分配,然后在他们不再被使用时"自动" ...

  9. Android学习笔记_62_手机安全卫士知识点归纳(2)ListView重要属性 PopupWindow应用

    1.缓存颜色: 为什么ListView在拖动的时间是黑色,而静止时间是自己的颜色是因为 ListView的缓存.只需一个配置即可.在这个ListView里面加上它即可. android:cacheCo ...

  10. div鼠标悬停,子元素上移,鼠标移出,子元素下移动画。

    HTML: <div class="edt_title" > <div id="edt_title"> <p class=&quo ...