http://www.tuicool.com/articles/bI3IBv
附问题:有以下一个SQL语句:
SELECT * FROM ( SELECT t.*, row_number() OVER (ORDER BY ID) rn FROM mytable t ) WHERE rn BETWEEN :start and :end
sql中的order by语句大大降低了处理的速度,如果把order by去掉,相应的执行计划会大大地提高。如果换成下面的sql:
SELECT t.*, row_number() OVER (ORDER BY ID) rn FROM mytable t WHERE rownum BETWEEN :start and :end
很明显,这个sql是错的,根本查询不了正确的数据信息。是否有其它的方法可以提高查询速度?
针对以上问题,就必须要了解一下关于row_number和rownum的区别,以及如何来运用这些信息。
首先了解一下rownum是如何进行工作的,根据oracle的官方文档:
如果对rownum进行大于比较,这个比较将直接返回false。如,下列sql语句将不能返回任何数据信息:
SELECT * FROM employees WHERE ROWNUM > 1
在查询中,第一条被命中的数据将赋予一个伪列rownum为1,那么这个条件就为false。第二条被命中的数据由于第一条的false将重新成为第一条数据,那么仍然赋值为1,显示这个条件仍然为false。后续所有的数据将重复执行这个逻辑,最后一条数据也没有返回。
这就是为什么之前的第2个查询,应该转换为以下的sql语句:
SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM mytable t ORDER BY paginator, id ) WHERE rn BETWEEN :start and :end
接下来,需要通过创建一些临时数据表来查看这个sql语句的执行性能,我们将创建临时表,追加索引,然后填充数据,最后分析这个sql语句的查询信息。
CREATE TABLE mytable ( id NUMBER(10) NOT NULL, paginator NUMBER(10) NOT NULL, value VARCHAR2(50) ) / ALTER TABLE mytable ADD CONSTRAINT pk_mytable_id PRIMARY KEY (id) / CREATE INDEX ix_mytable_paginator_id ON mytable(paginator, id) / INSERT INTO mytable(id, paginator, value) SELECT level, level / 10000, ‘Value ‘ || level FROM dual CONNECT BY level <= 1000000 / COMMIT / BEGIN DBMS_STATS.gather_schema_stats(‘"20090506_rownum"‘); END; /
这个Sql语句创建一个包括100万条数据的表,并且创建一个联合索引.
同时,在这个查询中,patinator字段是不是惟一的,是为了在之后展示这样一种现象:
在查询中,某些数据可能在不同的分页查询中出现多次,而某些数据则可能根据不会被查询出
这就是所谓的分页混乱。
然后,分别使用row_numer和rownum分别进行查询,返回从900001到900010之间的10条数据信息。
row_number()
SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (ORDER BY paginator, id) AS rn FROM mytable t ) WHERE rn BETWEEN 900001 AND 900010
ID | PAGINATOR | VALUE | RN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
900001 | 90 | Value 900001 | 900001 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900002 | 90 | Value 900002 | 900002 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900003 | 90 | Value 900003 | 900003 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900004 | 90 | Value 900004 | 900004 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900005 | 90 | Value 900005 | 900005 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900006 | 90 | Value 900006 | 900006 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900007 | 90 | Value 900007 | 900007 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900008 | 90 | Value 900008 | 900008 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900009 | 90 | Value 900009 | 900009 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900010 | 90 | Value 900010 | 900010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10 rows fetched in 0.0005s (0.8594s) |
SELECT STATEMENT VIEW WINDOW NOSORT STOPKEY TABLE ACCESS BY INDEX ROWID, 20090506_rownum.MYTABLE INDEX FULL SCAN, 20090506_rownum.IX_MYTABLE_PAGINATOR_ID
rownum
SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM ( SELECT * FROM mytable ORDER BY paginator, id ) t ) WHERE rn BETWEEN 900001 AND 900010
ID | PAGINATOR | VALUE | RN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
900001 | 90 | Value 900001 | 900001 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900002 | 90 | Value 900002 | 900002 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900003 | 90 | Value 900003 | 900003 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900004 | 90 | Value 900004 | 900004 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900005 | 90 | Value 900005 | 900005 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900006 | 90 | Value 900006 | 900006 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900007 | 90 | Value 900007 | 900007 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900008 | 90 | Value 900008 | 900008 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900009 | 90 | Value 900009 | 900009 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900010 | 90 | Value 900010 | 900010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10 rows fetched in 0.0005s (0.7058) |
SELECT STATEMENT VIEW COUNT VIEW TABLE ACCESS BY INDEX ROWID, 20090506_rownum.MYTABLE INDEX FULL SCAN, 20090506_rownum.IX_MYTABLE_PAGINATOR_ID
从上文中,可以看出,使用rownum的查询速度略快于row_number函数。
然后再看一个row_number查询,可以看出oracle足够的智能,它可以通过使用联合索引而避免进行排序操作,然后通过使用stopkey操作,可以直接快速查找到相应的数据信息。
rownum查询也同样使用索引,但并没有利用stopkey条件,只是简单的计数操作。
那
么,能否同样让rownum使用stopkey呢。在之前的查询中,oracle并不知道这个rn就是在内层查询rownum的别名,我们可以重写查询,
在外层查询中使用rownum,这样就可以在外层利用stopkey条件了。这就是我们常见的oracle3层分页的变形:
SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM ( SELECT * FROM mytable ORDER BY paginator, id ) t ) WHERE rn >= 900001 AND rownum <= 10
ID | PAGINATOR | VALUE | RN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
900001 | 90 | Value 900001 | 900001 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900002 | 90 | Value 900002 | 900002 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900003 | 90 | Value 900003 | 900003 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900004 | 90 | Value 900004 | 900004 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900005 | 90 | Value 900005 | 900005 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900006 | 90 | Value 900006 | 900006 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900007 | 90 | Value 900007 | 900007 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900008 | 90 | Value 900008 | 900008 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900009 | 90 | Value 900009 | 900009 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
900010 | 90 | Value 900010 | 900010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10 rows fetched in 0.0005s (0.4714s) |
SELECT STATEMENT COUNT STOPKEY VIEW COUNT VIEW TABLE ACCESS BY INDEX ROWID, 20090506_rownum.MYTABLE INDEX FULL SCAN, 20090506_rownum.IX_MYTABLE_PAGINATOR_ID
在这个查询中,oracle利用了stopkey,同时速度只有471ms,比原来更快。
如果row_number和rownum使用同样的执行计划,但为什么rownum明显更快呢。
这是因为:oracle的历史实在是太久了,而不同的时间导致相同的特性却有不同的效果。
rownum在oracle6中被引进,发布时间为1988年,在当时什么资源和条件都不满足的情况下,作为一个简单的计数器,被认为是非常简单和高效的。
而随着时代的发展,更多的需求被提及出来,这时,一个相当于但功能比rownum更强大的函数被引入,这就是row_number函数,它从oracle9i开始被引进。这时,效率已经不再是惟一的条件了,所以row_number的实现也不再以效率为惟一的指标了。
当然,如果你有更多的要求,如分组排序等,则需要使用row_number函数,但如果你仅仅是简单的分页查询,建议使用
rownum,这也是为什么在现在的时代rownum还是这么流行(据说在oracle12c中有offset分页操作符了,内部同样使用
row_number函数,这样rownum可以退休了)
以下是英文原文:http://explainextended.com/2009/05/06/oracle-row_number-vs-rownum/