工做中遇到的问题:sql
为调用方提供一个分页接口时,调用方一直反应有部分数据取不到,且取到的数据有重复的内容,因而我按如下步骤排查了下错误。数据库
1.检查分页页码生成规则是否正确。
2.检查SQL语句是否正确。(后来确认是SQL中order by做祟,犯了想固然的错误,认为SQL是最不可能出问题的地方,由于分页SQL格式与老代码分页SQL格式同样,因此没有怀疑。)
3.检查调用方入参是否正确。
4.检查调用方循环遍历边界。
5.在上述步骤验证没问题后,怀疑ibatis,调试到ibatis中,花费大量时间。
6.再次验证SQL,发现问题。
通过这么多步骤,发现本身考虑问题都想复杂了,最简单的错误缘由每每就是其错误缘由,那么咱们就来分析为何 order by 会形成分页SQL出错。oracle
分页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 的结果。spa
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 发生了变化code
因而这样在第三步,咱们取第#startRow#个数据后的全部数据时,就会一直把最后面的“凉血止血”相似的数据给取出来,致使出现重复的错误,而且前面的数据会有取不到的可能性。那么为何rownum会发生变化呢?blog
对于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生成不一致ci
解决办法:
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可能会很慢。