您的位置:首页 > 其它

Rownum产生原理及应用

2013-11-05 23:14 225 查看
使用过Oracle数据库的人都知道,在每一个查询中,数据库都会为我们产生一个称为rownum的伪列(当然,对于不同的版本和表类型,还有别的伪列)。对于这个伪列,尤其对于刚刚学习Oracle数据库的新人来说,可能会有很多难于理解的奇异现象,例如在这个列上使用大于(>)操作有时候可以,有时候不行,等于操作也是如此,有点背离我们一般的认知。我刚开始学习的时候也是很迷惑为什么会出现这种现象,一直迷惑到了现在。

在开始介绍rownum的原理之前,必须先准备一下环境(使用任何一个有建表权限的用户登录一个测试数据库)

1.创建表T create table T(id integer,name varchar2(10));

2.初始化测试数据(9999条测试数据)insert into T(id,name) select level,'T'||(level %37) from dual connect by level<10000

这样环境就准备好了,在开始之前,先想一下下面几条语句那几个是能取到数据的:

1.select * from T where rownum<10

2.select * from T where rownum>0

3.select * from T where rownum=1

4.select * from T where rownum=2

5.select * from T where rownum>1

在真实的Oracle表中并没有名字为rownum这个列,所以在insert,update,delete中我们都访问不到该列(这里有个疑问,因为update和delete也涉及到查询,不知道能不能够访问该列,但是在我经历过的应用中还没有实用的案例),只有在select中我们才能访问这个伪列。那么这个伪列是怎么产生的呢?其实,这个伪列是Oracle为我们产生输出的时候加上去的,也就是说在我们的查询中,Oracle先把rownum变量置成1,然后循环遍历表T中的数据,检查数据是否满足where中的条件,如果满足,则数据该数据,并将rownum变量的值增加1,一直到所有的数据遍历完成为止。用伪代码表示如下:

rownum = 1;

loop  < T >

if rec 满足 where条件 then

 输出rec

 rownum=rownum+1

end.

下面就用这个原理来看看上面的几个SQL语句

语句1:由于使用rownum<10作为where子句的条件,第一次进入循环的时候rownum的值为1,所以是满足条件的,然后rownum增加1,以此类推,可以取到9条数据

语句2:使用rownum>0作为条件,由于rownum初始值是1,随着循环,rownum的值是增加的,所以这个条件对所有数据都成立,这个语句取到的是全表的数据

语句3:使用rownum=1作为条件,只有第一次循环满足条件,所以只能取第一条数据

语句4:很容易理解,一条数据都取不到

语句5:一条都取不到

根据上面的原理,由于rownum产生的很多困惑的问题都可以理解了。但是仅仅理解还不够,下面在继续看两个SQL语句,看看大家能不能看出其中的不同(类似SQL中的TOP)

1.select * from (select rownum rn,id,name from T order by id desc) a where rownum<10

2.select * from ((select rownum rn,id,name from T order by id desc) a ) b where rn<10

上面这两个SQL语句都能够取到按照id值降序的前9条数据,但是写法有点不一样;除了写法之外,这里还涉及到oracle排序优化的问题,下面就详细对于每个语句的运行原理进行说明:

语句1:Oracle优化器会有一个将rownum<10 的条件推到内联视图中去的操作,也就是说,rownum<10这个条件不但会在视图a上起作用,而且对于视图内容的排序操作也起作  用,但是不是简单的将这个条件应用的内部视图上,如select rownum rn,id,name from T where rownum<10 order by id desc,这个是不对的。而是Oracle根据一个特定的算法,按照order by id desc的顺序,取出9条数据,这9条数据有可能是有序的,有可能是无序的,然后再对这9条数据进行排序(排序的数据量大大的减少),所以效率会比较高,推荐使用。但是又出现新的问题了,如果排序的字段的值有重复的,会出现什么样的现象呢?

 

语句2:Oracle根本没有优化,先对全表的数据根据id进行排序,然后把排序后的结果根据rn<10过滤,然后取出10条数据。这个语句的效率不是很高,但是很稳定。为什么这么说呢?如果排序列的值有重复(如,我们使用name列进行排序),只要数据不发生变化,无论我们怎么改变rn的条件,同一条数据的排序位置是不会发生变化的。

下面再对语句1中如果排序列的值有重复会出现什么情况做一下说明,因为是全表(或者按照索引)读取数据,读取数据的顺序是没有规律的,因为涉及到数据整理,块移动,还有一些DML操作造成的块连接等等,也就是说语句1中根据order by子句中取出数据的顺序是不确定的(取10条和取20条是同一条数据所在的位置是可能不一样的),如果单纯的使用个语句取前几条数据,这个是没有问题的。但是如果用来实现分页,如

select * from ((select rownum rn,id,name from T order by name desc) a where rownum<21 b where rn>10

select * from ((select rownum rn,id,name from T order by name desc) a where rownum<11 where rn>0

上面这两条语句实现分页就会出现问题,比如在rownum<11的时候取到的id值为9988的记录在第一页上面,在rownum<21的时候取出的id值为9988的记录出现在了第二页上面,这都是有可能的。这种现象还有可能造成某些数据永远取不到。所以,如果排序子句用的列的值(或者组合值)有重复,如果要实现分页,必须使用语句2,虽然效率不是很高,但是不会出现业务逻辑上的混乱

 

当然,强烈建议排序子句中的值是唯一的,这样数据发生变化时,同一记录的位置是唯一的,这样就可以使用语句1来实现高效的分页。

注释:

1.文章中的不稳定和不确定不是指Oracle的排序算法,算法的稳定性可以参考数据结构相关的数据

2.Oracle优化器在特别复杂的SQL语句中会不会将rownum<n的条件推到内联视图中,需要在实践中确认

3.Oracle根据rownum<n条件取前n条数据然后再排序的操作称为SORT ORDER BY STOPKEY

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: