oracel 分页趣事

背景

在项目开发过程中,发现一个oracle分页的BUG,当然并不是oracle的BUG,是项目封装分页语句的BUG。跟踪代码的时候认真分析了sql语句,发现挺有意思的,所以再次做个笔记

oracle 分页原理

数据库中有物理表,有临时表,每一次查询都会生成一张临时表,oracle中每一次查询生产的临时表都自带一个字段rownum。oracle的分页都是利用该字段来实现分页的。

select rownum,W_sjbzb.* from W_sjbzb

image.png
如上图,每一次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,得到的结果如下图
image.png

注意观察图中的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语句上来看应该都没问题吧。但是其实不然,我们来看下结果。
image.png

注意观察数据,按照上面的排序然后分页得到的数据,很明显不是我们排序后的前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的值是乱的,如下图
    image.png

  • 接着数据会按照where过滤条件筛选出rownum <=10的10条数据,但是注意此时的表集合的rownum是乱的,所以此时筛选出来的数据就相当于是没有排序的数据。然后会重新生成一张临时表,因为生成了新的临时表,所以临时表的rownum被重置。所以得到的结果是下图
    image.png

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  ;

结果如下图
image.png

在此基础上,我们在来添加分页条件。注意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

image.png
看看上图的分页结果。最终得到的分页是排序后的数据。

所以数据库的sql语句执行逻辑以及一些基本的原理还是很重要的,不然当出现一些看似很显而易见的问题时,反而会不明所以。

# oracle   java   分页  

评论

公众号:mumuser

企鹅群:932154986

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×