还算深度解析ORACLE ROWNUM

  • Post author:
  • Post category:其他


关于Oracle的rownum大家并不陌生,很多人说是一个

查询出结果后的伪列

,是从1开始的,使用rownum过滤时必须从1开始等。rownum是怎样一个伪列,我们一会讨论;rownum是从1开始的,但是既然是伪列,为什么必须从1开始,下面我们一点点剖析。

本此博客主要讨论以下几个问题

①,rownum是什么及简要算法;

②,因为rownum,执行计划中COUNT STOPKEY是怎么回事;

③,利用rownum的COUNT STOPKEY进行分页SQL优化。


0,数据准备

drop table test1 ;

create table test1 as select * from dba_objects;

alter table TEST1 add constraint PK_TEST1 primary key (OBJECT_ID);

drop table test2;

create table test2 as select * from test1 where rownum<100; –只插入99条数据


1,一位美女的问题引出rownum是什么

前几天,有位美女提出了类似这样一个问题:

select * from test2 t where rownum=trunc(dbms_random.value(0,100));

这个语句实际返回结果是0-n条不等,按照很多人的解释,rownum过滤时必须从1开始,用随机数做条件,返回结果只能时0条或者1条,大于1条是怎么回事?

当我们不知道原因时,首先看看执行计划,看看数据库是怎么执行这句SQL的。

看这个执行计划,挺正常,一打眼,也看不出眉目。这里有个不起眼但是很重要的信息,

TABLE ACCESS FULL




一会我们说完rownum算法时,再回头说。

那我们先讨论rownum是什么吧。首先,我给rownum下另一个定义,

计数器!!!

简要算法举例:

我们首先构造个学生表,

select * from 学生表 where 性别=男 and rownum<4;

运算过程如下,虽然枯燥,但是请逐行读完,这个地方对理解rownum很关键。

1,定义 rownum变量,初始值为1;

2,逐行扫描学生表:

2.1 学号为1的学生性别不满足,丢掉;

2.2找到2号学生时,性别满足,rownum的值为1,小于4,数据取出,并在这一列标

识上此时rownum的值,也就是1;

2.3 继续找下一行,性别不满足,丢掉;

2.4 找到学号为4的学生,性别满足,rownum值+1,此时rownum为2,小于4,数

据取出,并在数据上标识此时rownum的值2;

2.5 找到学号为5的学生,性别满足,rownum值+1,此时rownum为3,小于4,数

据取出,并在数据上标识此时rownum的值3;

2.6 找到6号学生,性别不满足,丢弃;

2.7找到7号学生,性别满足,rownum值+1,此时rownum值为4,不满足检索条件

rownum<4,截止目前,能够满足条件的数据已经全找出,结束查询。

通过这个运算过程,解释了rownum过滤为什么要从1开始,因为假设where rownum=2,

在上面2.2那个地方,就是取第一条结果是,就不满足rownum的条件了,查询就结束了;

通过上面的运算过程,也解释了大家为什么会称rownum为

查询出结果后的伪列

了,需要重点声明,这个所谓的查询出结果后,不是最后查询出结果后,而是逐行检索,取到满足条件(rownum条件和其他条件都满足)数据时加上的,即上面的

2.2


,2.4,2.5

那么,我们回到美女的问题,为什么用随机数做为rownum条件过滤时结果不固定呢?

此时该解释上面我们留下的问题,

TABLE ACCESS FULL

了。因为在查询过程时,

扫一行数据,产生一个随机数,随机数是1时,结果才保留下来

,当然,最后返回数据量不确定了。

当然,数据量最够大,但是随机数范围较小时,一般取到的行数=随机数最大值。例如用下面的test1(数据7万多条,随机数0-100),一般会返回99条;

select * from test1 t where rownum=trunc(dbms_random.value(0,100));

那么问题又来了,7万多次扫描,随机数产生1的机会应该不止99次吧,理论上700次左右吧,为什么几乎全是返回99条呢?

我个人猜想,Oracle内部做了优化处理,即使是满足条件的行数大于随机数的最大值,数据库也只返回随机数最大值以内的行数。至于猜想对不对,还请牛人指点。


2. 执行计划中COUNT STOPKEY

执行下面的SQL语句

select * from test1 t where  rownum<10;

这里的COUNT STOPKEY就是【1,一位美女的问题引出rownum是什么】的【2.7找到7号学生,性别满足,rownum值+1,此时rownum值为4,不满足检索条件rownum<4,截止目前,能够满足条件的数据已经全找出,结束查询。】,也就是不满足rownum条件后,查询结束。


3. 利用rownum的COUNT STOPKEY进行分页SQL优化

其实,上面我们大篇幅说了rownum的原理,很多人该吐槽了,就是一程序员,又不是什么教授学究,搞这种算法有意义吗?

答案是有,我们这里就用rownum进行分页查询SQL优化,假设不懂rownum原理,我们只能生搬硬套高性能的分页写法。

先来两套分页写法,性能一好一坏。

性能坏的写法:

select *

from (select rownum as rn , t1.*

from test1 t1

order by t1.object_id asc)

where rn between 11 and 30;

性能好的写法

select *

from (select rownum as rn, t.*

from(select t1.*

from test1 t1

order by t1.object_id asc) t

where rownum < 30) tt

where rn > 11;

执行时间和逻辑读改善一目了然。

细心的朋友会发现,这里有个 INDEX FULL SCAN,其实之前,我也一直困惑INDEX FULL SCAN的使用场景,因为索引全扫描一般回表产生单块读(不回表就是INDEX FAST FULL SCAN了),全扫描就是全部回表;但是像ORACLE这种大厂,里面的研发人员都是技术相当牛的角色,他们肯定不会留着没有用的处理。看到这种高性能分页写法,还请教了SQL优化牛人落落(罗炳森)老师我才明白索引全扫描的好处,其实就是在分页查询时利用索引的有序配合rownum的COUNT STOPKEY来降低IO,至于索引全扫描还有没有其他的高性能使用场景,也请牛人指教。



版权声明:本文为songjian1104原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。