背景
在项目开发过程中,发现一个oracle分页的BUG,当然并不是oracle的BUG,是项目封装分页语句的BUG。跟踪代码的时候认真分析了sql语句,发现挺有意思的,所以再次做个笔记
oracle 分页原理
数据库中有物理表,有临时表,每一次查询都会生成一张临时表,oracle中每一次查询生产的临时表都自带一个字段rownum。oracle的分页都是利用该字段来实现分页的。
select rownum,W_sjbzb.* from W_sjbzb
如上图,每一次select查询都会有一个rownum字段,其实就是一个index下标索引。如果我们需要分页的话最简单的添加rownum的过滤条件。
select rownum,W_sjbzb.* from W_sjbzb where rownum <= 10
如果分页过程中需要排序呢?也很简单只要先排序在分页即可。其实没那么简单,有些事情看起来很简单其实隐藏着很多秘密。
我们直接来看一个示例,我们先写一条只有排序没有分页的sql语句,查询下看看得到的表集合
SELECT * FROM ( select rownum,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC ) T
上述的sql语句比较简单,就是做了from子查询,此时会有一张临时表T,得到的结果如下图
注意观察图中的rownum和排序后的数据,已经用红色框框标识出来。rownum应该还是W_sjbzb原始数据的rownum,排序后,rownum也跟着参与了排序。如果此时我要分页,那么理所当然的sql语句应该是下面这个样子:
SELECT * FROM ( select rownum,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC ) T where rownum <=10
看到这里大家是不是觉得理所当然,应该没问题吧,分页,排序从sql语句上来看应该都没问题吧。但是其实不然,我们来看下结果。
注意观察数据,按照上面的排序然后分页得到的数据,很明显不是我们排序后的前10条数据(具体请参照没有分页只有排序的没有分页的sql语句查询得到的前10条数据)。而且很奇怪,rownum被重置了,也就是说有了一张新的临时表。
如果分页是这么写的sql的话,很显然排序分页就会有逻辑问题。
- 重点解释一下
sql语句每一次查询,如果没有where过滤条件,仅仅是排序,不会生成新的临时表,所以rownum还是原来物理表的值,排序后,rownum并不是按照排序后的顺序重新生成下标,依然是原有数据的rownum值且跟着一起排了序
如果有where过滤条件,那么就会重新生成一张临时表,rownum也会跟着新的临时表按照数据顺序重置。
所以我们来重新审视下下面这条sql语句,注意sql语句中表达式的的执行顺序(这里不介绍了)。我们来还原下整个过程
SELECT * FROM ( select rownum,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC ) T where rownum <=10
-
首先是将表W_sjbzb数据进行排序并得到一张结果表,但并不生成临时表,rownum依然是W_sjbzb表的rownum,所以rownum的值是乱的,如下图
-
接着数据会按照where过滤条件筛选出rownum <=10的10条数据,但是注意此时的表集合的rownum是乱的,所以此时筛选出来的数据就相当于是没有排序的数据。然后会重新生成一张临时表,因为生成了新的临时表,所以临时表的rownum被重置。所以得到的结果是下图
select永远是最后执行,也就是说生成了临时表,然后从表中选择展示字段返回结果集。所以图中rownum是新的临时表的rownum,但是where条件中的rownum却是W_sjbzb表原先的rownum。
回到主题,既然这些分页是有问题的,那么如何正确编写分页语句呢。我们需要使用子查询强行生成一个新的rownum。注意下面的sql语句
select rownum ,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC;
SELECT ROWNUM, T.* FROM ( select rownum ,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC ) T ;
结果如下图
在此基础上,我们在来添加分页条件。注意select永远是最后执行,所以我们还得在套一层子查询。sql语句如下(代码中最后一条sql,为了方便对比我放了三条sql):
select rownum ,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC ;
SELECT ROWNUM, T.* FROM ( select rownum ,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC ) T ;
select ROW_.* from ( SELECT ROWNUM, T.* FROM ( select rownum as rownum_ ,W_sjbzb.* from W_sjbzb ORDER BY F_xh ASC ) T ) ROW_ WHERE ROWNUM <=10
看看上图的分页结果。最终得到的分页是排序后的数据。
所以数据库的sql语句执行逻辑以及一些基本的原理还是很重要的,不然当出现一些看似很显而易见的问题时,反而会不明所以。