Oracle ROWNUM用法和分页查询总结

**********************************************************************************************************

 NESTED LOOP/HASH JOIN/SORT MERGE JOIN的区别  

今天遇到一个SQL问题,耗时很久,CPU开销很大。

通过分析,发现本该采用nestedloop的语句,结果采用了hashjoin

修改后效果显著。顺便转一篇关于连接的文章。

SQL:

SELECT T1.ID
FROM   V_GISODFCONNECTOR T1, GISCONNECTORASSOC T2
WHERE  T1.ID = T2.CONNECTORID
       AND T2.CONNECTEDENTITYTYPE = 'F'
       AND (T2.CONNECTEDENTITYID IN
       (SELECT ID
             FROM   GISFIBER
             WHERE  FUSEFIBERID = :1
                    AND STATUS = :2) OR
       (T2.CONNECTEDENTITYID IN
       (SELECT ID
              FROM   GISFIBER
              WHERE  FUSEFIBERID = :3
                     AND STATUS = :4)))

   

修改后:

SELECT T1.ID
FROM   V_GISODFCONNECTOR T1, GISCONNECTORASSOC T2
WHERE  T1.ID = T2.CONNECTORID
       AND T2.CONNECTEDENTITYTYPE = 'F'
       AND (T2.CONNECTEDENTITYID IN
       (SELECT ID
             FROM   GISFIBER
             WHERE  (FUSEFIBERID = :1 AND STATUS = :2)
                    OR (FUSEFIBERID = :3 AND STATUS = :4)))

多表之间的连接有三种方式:Nested LoopsHash Join Sort Merge Join. 下面来介绍三种不同连接的不同:

一. NESTED LOOP:
对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择。在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表中检索找到与它匹配的行,因此整个查询返回的结果集不能太大(大于1 万不适合),要把返回子集较小表的作为外表(CBO 默认外表是驱动表),而且在内表的连接字段上一定要有索引。当然也可以用ORDERED 提示来改变CBO默认的驱动表,使用USE_NL(table_name1 table_name2)可是强制CBO 执行嵌套循环连接。
Nested loop
一般用在连接的表中有索引,并且索引选择性较好的时候.
步骤:确定一个驱动表(outer table),另一个表为inner table,驱动表中的每一行与inner表中的相应记录JOIN。类似一个嵌套的循环。适用于驱动表的记录集比较小(<10000)而且inner表需要有有效的访问方法(Index)。需要注意的是:JOIN的顺序很重要,驱动表的记录集一定要小,返回结果集的响应时间是最快的。
cost = outer access cost + (inner access cost * outer cardinality)
| 2 | NESTED LOOPS | | 3 | 141 | 7 (15)|
| 3 | TABLE ACCESS FULL | EMPLOYEES | 3 | 60 | 4 (25)|
| 4 | TABLE ACCESS BY INDEX ROWID| JOBS | 19 | 513 | 2 (50)|
| 5 | INDEX UNIQUE SCAN | JOB_ID_PK | 1 | | |
EMPLOYEES
outer table, JOBSinner table.
二. HASH JOIN :
散列连接是CBO 做大数据集连接时的常用方式,优化器使用两个表中较小的表(或数据源)利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。
这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和。但是在表很大的情况下并不能完全放入内存,这时优化器会将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要有较大的临时段从而尽量提高I/O 的性能。
也可以用USE_HASH(table_name1 table_name2)提示来强制使用散列连接。如果使用散列连接HASH_AREA_SIZE 初始化参数必须足够的大,如果是9iOracle建议使用SQL工作区自动管理,设置WORKAREA_SIZE_POLICY AUTO,然后调整PGA_AGGREGATE_TARGET 即可。
Hash join
在两个表的数据量差别很大的时候.
步骤:将两个表中较小的一个在内存中构造一个HASH表(对JOIN KEY),扫描另一个表,同样对JOIN KEY进行HASH后探测是否可以JOIN。适用于记录集比较大的情况。需要注意的是:如果HASH表太大,无法一次构造在内存中,则分成若干个partition,写入磁盘的temporary segment,则会多一个写的代价,会降低效率。
cost = (outer access cost * # of hash partitions) + inner access cost
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 665 | 13300 | 8 (25)|
| 1 | HASH JOIN | | 665 | 13300 | 8 (25)|
| 2 | TABLE ACCESS FULL | ORDERS | 105 | 840 | 4 (25)|
| 3 | TABLE ACCESS FULL | ORDER_ITEMS | 665 | 7980 | 4 (25)|
--------------------------------------------------------------------------
ORDERS
HASH TABLEORDER_ITEMS扫描
三.SORT MERGE JOIN
通常情况下散列连接的效果都比排序合并连接要好,然而如果行源已经被排过序,在执行排序合并连接时不需要再排序了,这时排序合并连接的性能会优于散列连接。可以使用USE_MERGE(table_name1 table_name2)来强制使用排序合并连接.
Sort Merge join
用在没有索引,并且数据已经排序的情况.
cost = (outer access cost * # of hash partitions) + inner access cost
步骤:将两个表排序,然后将两个表合并。通常情况下,只有在以下情况发生时,才会使用此种JOIN方式:
1.RBO
模式
2.
不等价关联(>,<,>=,<=,<>)
3.HASH_JOIN_ENABLED=false
4.
数据源已排序
. 三种连接工作方式比较:
Hash join
的工作方式是将一个表(通常是小一点的那个表)做hash运算,将列数据存储到hash列表中,从另一个表中抽取记录,做hash运算,到hash 列表中找到相应的值,做匹配。
Nested loops
工作方式是从一张表中读取数据,访问另一张表(通常是索引)来做匹配,nested loops适用的场合是当一个关联表比较小的时候,效率会更高。
Merge Join
是先将关联表的关联列各自做排序,然后从各自的排序表中抽取数据,到另一个排序表中做匹配,因为merge join需要做更多的排序,所以消耗的资源更多。 通常来讲,能够使用merge join的地方,hash join都可以发挥更好的性能。
**********************************************************************************************************

ROWNUM
可能都知道ROWNUM只适用于小于或小于等于,如果进行等于判断,那么只能等于1,不能进行大于的比较。
ROWNUM是oracle系统顺序分配为从查询返回的行的编号,返回的第一行分配的是1,第二行是2,依此类推。
ROWNUM总是从1开始,不管当前的记录是否满足查询结果,ROWNUM返回的值都是1,如果这条记录的值最终满足所有的条件,那么ROWNUM会递加,下一条记录的ROWNUM会返回2,否则下一条记录的ROWNUM仍然返回1
理解了这一点,就清楚为什么一般的ROWNUM大于某个值或等于某个不为1的值是无法返回结果的,因此对于每条记录的ROWNUM都是1,而ROWNUM1不满足查询的结果,所以下一条记录的ROWNUM不会递增,仍然是1,因此所有的记录都不满足条件。
分页查询格式1

在查询的最外层控制分页的最小值和最大值。查询语句如下:

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM TABLE_NAME) A

)

WHERE RN BETWEEN 21 AND 40


分页查询格式2

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM TABLE_NAME) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21


分页查询格式3
考虑到多表联合的情况,如果不介意在系统中使用HINT的话,可以将分页的查询语句改写为:

SELECT /*+ FIRST_ROWS */ * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM TABLE_NAME) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21


效率问题
对比这两种写法,绝大多数的情况下,第2
个查询的效率比第1个高得多。
这是由于CBO优化模式下,Oracle可以将外层的查询条件推到内层查询中,以提高内层查询的执行效率。对于第2个查询语句,第二层的查询条件WHERE ROWNUM <= 40就可以被Oracle推入到内层查询中,这样Oracle查询的结果一旦超过了ROWNUM限制条件,就终止查询将结果返回了。
而第1个查询语句,由于查询条件BETWEEN 21 AND 40是存在于查询的第三层,而Oracle无法将第三层的查询条件推到最内层(即使推到最内层也没有意义,因为最内层查询不知道RN代表什么)。因此,对于第1个查询语句,Oracle最内层返回给中间层的是所有满足条件的数据,而中间层返回给最外层的也是所有数据。数据的过滤在最外层完成,显然这个效率要比第一个查询低得多。
上面分析的查询不仅仅是针对单表的简单查询,对于最内层查询是复杂的多表联合查询或最内层查询包含排序的情况一样有效。

观察上面格式1和格式2二者的执行计划可以发现,两个执行计划唯一的区别就是格式2的查询在COUNT这步使用了STOPKEY,也就是说,OracleROWNUM <= 20推入到查询内层,当符合查询的条件的记录达到STOPKEY的值,则Oracle结束查询。因此,可以预见,采用第二种方式,在翻页的开始部分查询速度很快,越到后面,效率越低,当翻到最后一页,效率应该和第一种方式接近。

分页查询语句之所以可以很快的返回结果,是因为它的目标是最快的返回第一条结果。如果每页有20条记录,目前翻到第5页,那么只需要返回前100条记录都可以满足查询的要求了,也许还有几万条记录也符合查询的条件,但是由于分页的限制,在当前的查询中可以忽略这些数据,而只需尽快的返回前100条数据。这也是为什么在标准分页查询语句中经常会使用FIRST_ROWS提示的原因。
对于行操作,可以在得到结果的同时将结果直接返回给上一层调用。但是对于结果集操作,Oracle必须得到结果集中所有的数据,因此分页查询中所带的ROWNUM信息不起左右。如果最内层的子查询中包含了下面这些操作中的一个以上,则分页查询语句无法体现出任何的性能优势:UNIONUNION ALLMINUSINTERSECTGROUP BYDISTINCTUNIQUE以及聚集函数如MAXMIN和分析函数等。

Oracle10g
的新功能GROUP BY STOPKEY,使得Oracle10g解决了GROUP BY操作分页效率低的问题。在10g以前,OracleGROUP BY操作必须完全执行完,才能将结果返回给用户。但是Oracle10g增加了GROUP BY STOPKEY执行路径,使得用户在执行GROUP BY操作时,可以根据STOPKEY随时中止正在运行的操作。这使得标准分页函数对于GROUP BY操作重新发挥了作用。

除了这些操作以外,分页查询还有一个很明显的特点,就是处理的页数越小,效率就越高,越到后面,查询速度越慢。
分页查询用来提高返回速度的方法都是针对数据量较小的前N条记录而言。无论是索引扫描,NESTED LOOP连接,还是ORDER BY STOPKEY,这些方法带来性能提升的前提都是数据量比较小,一旦分页到了最后几页,会发现这些方法不但没有办法带来性能的提升,而且性能比普通查询还要低得多。这一点,在使用分页查询的时候,一定要心里有数。
分页查询一般情况下,很少会翻到最后一篇,如果只是偶尔碰到这种情况,对系统性能不会有很大的影响,但是如果经常碰到这种情况,在设计分页查询时应该给予足够的考虑。

多表联合
下面简单讨论一下多表联合的情况。对于最常见的等值表连接查询,CBO一般可能会采用两种连接方式NESTED LOOPHASH JOINMERGE JOIN效率比HASH JOIN效率低,一般CBO不会考虑)。
一般对于大表查询情况下,HASH JOIN的效率要比NESTED LOOP高很多,所以CBO一般默认会选择HASH JOIN.

但是如果分页查询的内层是这种连接查询的话,使用NESTED LOOP可以更快的得到前N条记录。
在这里,由于使用了分页,因此指定了一个返回的最大记录数,NESTED LOOP在返回记录数超过最大值时可以马上停止并将结果返回给中间层,而HASH JOIN必须处理完所有结果集(MERGE JOIN也是)。那么在大部分的情况下,对于分页查询选择NESTED LOOP作为查询的连接方法具有较高的效率(分页查询的时候绝大部分的情况是查询前几页的数据,越靠后面的页数访问几率越小)。


HASH JOIN
中第一步也就是第一张表的全表扫描是无法应用STOPKEY的,这就是NESTED LOOPHASH JOIN优势的地方。
但是,如果恰好第一张表很小,对这张表的全扫描的代价极低,会显得HASH JOIN效率更高。
如果两张表的大小相近,或者Oracle错误的选择了先扫描大表,则使用HASH JOIN的效率就会低得多。

因此对于表连接来说,在写分页查询的时候,可以考虑增加FIRST_ROWS提示,它会导致CBO选择NESTED LOOP,有助于更快的将查询结果返回。
其实,不光是表连接,对于所有的分页查询都可以加上FIRST_ROWS提示。
不过需要注意的时,分页查询的目标是尽快的返回前N条记录,因此,无论是ROWNUM还是FIRST_ROWS机制都是提高前几页的查询速度,
对于分页查询的最后几页,采用HASH JOIN的方式,执行效率几乎没有任何改变,而采用NESTED LOOP方式,则效率严重下降,而且远远低于HASH JOIN的方式。


排序列不唯一所带来的问题
如果用来排序的列不唯一,也就是存在值相等的行,可能会造成第一次在前10条返回记录中,某行数据出现了,而第二次在11到第20条记录中,某行数据又出现了。一条数据重复出现两次,就必然意味着有数据在两次查询中都不会出现。
其实造成这个问题的原因很简单,是由于排序列不唯一造成的。Oracle这里使用的排序算法不具有稳定性,也就是说,对于键值相等的数据,这种算法完成排序后,不保证这些键值相等的数据保持排序前的顺序。
解决这个问题其实也很简单。有两种方法可以考虑。
1
)在使用不唯一的字段排序时,后面跟一个唯一的字段。
一般在排序字段后面跟一个主键就可以了,如果表不存在主键,跟ROWID也可以。这种方法最简单,且对性能的影响最小。
2)另一种方法就是使用前面给出过多次的BETWEEN AND的方法。
这种方式由于采用表数据的全排序,每次只取全排序中的某一部分数据,因此不会出现上面提到的重复数据问题。
但是正是由于使用了全排序,而且ROWNUM信息无法推到查询内部,导致这种写法的执行效率很低


测试结果
下面做一些测试,按照如下步骤准备数据:

CREATE TABLE T AS SELECT * FROM DBA_USERS;

CREATE TABLE T1 AS SELECT * FROM DBA_SOURCE;

ALTER TABLE T ADD CONSTRAINT PK_T PRIMARY KEY (USERNAME);

ALTER TABLE T1 ADD CONSTRAINT FK_T1_OWNER FOREIGN KEY (OWNER) REFERENCES T(USERNAME);

CREATE INDEX IND_T1_OWNER ON T1(OWNER);

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T')

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T1')

set autotrace traceonly

set timing on


现在表格T中有37行数据,表格T1中有623K行数据。

比较格式1和格式2的查询计划

--查询语句1

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T1) A

)

WHERE RN BETWEEN 21 AND 40;

--查询语句2

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T1) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21;

 

  

执行计划

执行时间

统计信息

查询语句1

----------------------------------------------------------
Plan hash value: 3921461035

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |   623K|  1231M|  2879   (1)| 00:00:35 |
|*  1 |  VIEW               |      |   623K|  1231M|  2879   (1)| 00:00:35 |
|   2 |   COUNT             |      |       |       |            |          |
|   3 |    TABLE ACCESS FULL| T1   |   623K|    59M|  2879   (1)| 00:00:35 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN"<=40 AND "RN">=21)

00: 00: 02.40

1  recursive calls
0  db block gets
10441  consistent gets
10435  physical reads
0  redo size
1720  bytes sent via SQL*Net to client
431  bytes received via SQL*Net from client
3  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
20  rows processed

查询语句2

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |    40 | 82800 |     2   (0)| 00:00:01 |
|*  1 |  VIEW               |      |    40 | 82800 |     2   (0)| 00:00:01 |
|*  2 |   COUNT STOPKEY     |      |       |       |            |          |
|   3 |    TABLE ACCESS FULL| T1   |    40 |  4000 |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN">=21)
   2 - filter(ROWNUM<=40)

00: 00: 00.03

0  recursive calls
0  db block gets
6  consistent gets
20  physical reads
0  redo size
1720  bytes sent via SQL*Net to client
431  bytes received via SQL*Net from client
3  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
20  rows processed


关联查询

--查询语句1

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T, T1 WHERE T.USERNAME = T1.OWNER) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21;

--查询语句2

SELECT /*+ FIRST_ROWS */ * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T, T1 WHERE T.USERNAME = T1.OWNER) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21;

--或者

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT /*+ USE_NL(T T1) */ * FROM T, T1 WHERE T.USERNAME = T1.OWNER) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21;

可以看到默认是采用hash join,改用nested loop join方式似乎效率并没有明显提高,但是这是由于表T比较小只有34行,所以hash join的第一步即使对T进行全表扫描而无法应用stopkey,效率也很高。

  

执行计划

执行时间

统计信息

查询语句1

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |    40 |   165K|     6  (17)| 00:00:01 |
|*  1 |  VIEW                |      |    40 |   165K|     6  (17)| 00:00:01 |
|*  2 |   COUNT STOPKEY      |      |       |       |            |          |
|*  3 |    HASH JOIN         |      |    40 | 12400 |     6  (17)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| T    |    34 |  3740 |     3   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL| T1   |    40 |  4000 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN">=21)
   2 - filter(ROWNUM<=40)
   3 - access("T"."USERNAME"="T1"."OWNER")

00: 00: 00.04

0 recursive calls
0 db block gets
9 consistent gets
20 physical reads
0 redo size
2927 bytes sent via SQL*Net to client
431 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20 rows processed

查询语句2

-----------------------------------------------------------------------------------------------
| Id  | Operation                      | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |    40 |   165K| 13627   (1)| 00:02:44 |
|*  1 |  VIEW                          |              |    40 |   165K| 13627   (1)| 00:02:44 |
|*  2 |   COUNT STOPKEY                |              |       |       |            |          |
|   3 |    NESTED LOOPS                |              |       |       |            |          |
|   4 |     NESTED LOOPS               |              |   623K|   124M| 13627   (1)| 00:02:44 |
|   5 |      TABLE ACCESS FULL         | T            |    34 |  3740 |     3   (0)| 00:00:01 |
|*  6 |      INDEX RANGE SCAN          | IND_T1_OWNER | 36684 |       |    91   (0)| 00:00:02 |
|   7 |     TABLE ACCESS BY INDEX ROWID| T1           | 18342 |  1791K|   710   (1)| 00:00:09 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN">=21)
   2 - filter(ROWNUM<=40)
   6 - access("T"."USERNAME"="T1"."OWNER")

00: 00: 00.01

1 recursive calls
0 db block gets
14 consistent gets
0 physical reads
0 redo size
2927 bytes sent via SQL*Net to client
431 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20 rows processed


现在增大表T

ALTER TABLE T MODIFY(USER_ID NULL, ACCOUNT_STATUS NULL, DEFAULT_TABLESPACE NULL,

TEMPORARY_TABLESPACE NULL, CREATED NULL, PROFILE NULL);

INSERT INTO T(USERNAME) SELECT ('USER' || LEVEL) FROM DUAL CONNECT BY LEVEL < 100000;

COMMIT;

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T')

然后重新测试语句1,会发现现在oracle已经改成用nested loop join了。
因此现在语句1和语句2的效果等同了。可以使用 USE_HASH(T T1) HINT强制使用hash join,结果做下对比,会发现hash join的效率低于nested loop join,读数据发生的IOconsistent gets+physical reads)大大增加了.
可以看到CBO是相当智能了。

含排序的查询
含排序操作的分页查询。可以简单的将查询分为两种不同情况,第一种排序列就是索引列,这种可以利用索引读取,第二种排序列没有索引。
第一种情况又可以细分为:完全索引扫描和通过索引扫描定位到表记录两种情况。无论是那种情况,都可以通过索引的全扫描来避免排序的产生。
第二种情况下,排序不可避免,但是利用给出分页格式,Oracle不会对所有数据进行排序,而是只排序前N条记录。

--查询语句1,排序列就是索引列.注意这里需要加上OWNER IS NOT NULL,否则由于OWNER列不是NOT NULL,会导致索引无法使用。

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T1 WHERE OWNER IS NOT NULL ORDER BY OWNER) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21;

 

--查询语句2,排序列没有索引

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T1 ORDER BY NAME) A

WHERE ROWNUM <= 40

)

WHERE RN >= 21;

 

 

--查询语句3,排序列没有索引

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T1 ORDER BY NAME) A

)

WHERE RN BETWEEN 21 AND 40;

   

  

执行计划

执行时间

统计信息

查询语句1

-----------------------------------------------------------------------------------------------
| Id  | Operation                      | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |    40 | 82800 |     4   (0)| 00:00:01 |
|*  1 |  VIEW                          |              |    40 | 82800 |     4   (0)| 00:00:01 |
|*  2 |   COUNT STOPKEY                |              |       |       |            |          |
|   3 |    VIEW                        |              |    40 | 82280 |     4   (0)| 00:00:01 |
|   4 |     TABLE ACCESS BY INDEX ROWID| T1           |   646K|    62M|     4   (0)| 00:00:01 |
|*  5 |      INDEX FULL SCAN           | IND_T1_OWNER |    40 |       |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN">=21)
   2 - filter(ROWNUM<=40)
   5 - filter("OWNER" IS NOT NULL)

*排序列就是索引列,可以看到通过索引的全扫描来避免了排序的产生。

00: 00: 00.01

1 recursive calls
0 db block gets
8 consistent gets
1 physical reads
0 redo size
1682 bytes sent via SQL*Net to client
427 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20 rows processed

查询语句2

-----------------------------------------------------------------------------------------
| Id  | Operation                | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |      |    40 | 82800 |       | 18077   (1)| 00:03:37 |
|*  1 |  VIEW                    |      |    40 | 82800 |       | 18077   (1)| 00:03:37 |
|*  2 |   COUNT STOPKEY          |      |       |       |       |            |          |
|   3 |    VIEW                  |      |   646K|  1268M|       | 18077   (1)| 00:03:37 |
|*  4 |     SORT ORDER BY STOPKEY|      |   646K|    62M|    72M| 18077   (1)| 00:03:37 |
|   5 |      TABLE ACCESS FULL   | T1   |   646K|    62M|       |  3023   (1)| 00:00:37 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN">=21)
   2 - filter(ROWNUM<=40)
   4 - filter(ROWNUM<=40)

*排序列没有索引,排序不可避免。带STOPKEYORDER BY,排序操作放到了内存中,
在大数据量需要排序的情况下,要比不带STOPKEY排序的效率高得多。

00: 00: 01.32

1 recursive calls
0 db block gets
10973 consistent gets
10969 physical reads
0 redo size
2529 bytes sent via SQL*Net to client
427 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
20 rows processed

查询语句3

--------------------------------------------------------------------------------------
| Id  | Operation             | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |   646K|  1276M|       | 18077   (1)| 00:03:37 |
|*  1 |  VIEW                 |      |   646K|  1276M|       | 18077   (1)| 00:03:37 |
|   2 |   COUNT               |      |       |       |       |            |          |
|   3 |    VIEW               |      |   646K|  1268M|       | 18077   (1)| 00:03:37 |
|   4 |     SORT ORDER BY     |      |   646K|    62M|    72M| 18077   (1)| 00:03:37 |
|   5 |      TABLE ACCESS FULL| T1   |   646K|    62M|       |  3023   (1)| 00:00:37 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN"<=40 AND "RN">=21)

*排序列没有索引,排序不可避免,不带STOPKEY
进行的数据的全排序,排序数据量大,排序操作不得不在磁盘上完成,因此耗时比较多。

00: 00: 05.31

72 recursive calls
26 db block gets
10973 consistent gets
19933 physical reads
0 redo size
6489 bytes sent via SQL*Net to client
427 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
20 rows processed


排序列不唯一所带来的问题

tony@ORCL1> CREATE TABLE TEST AS SELECT ROWNUM ID, A.* FROM DBA_OBJECTS A;

Table created.

 

tony@ORCL1> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST');

PL/SQL procedure successfully completed.

 

tony@ORCL1> COLUMN OBJECT_NAME FORMAT A30

tony@ORCL1> SELECT * FROM

2 (

3 SELECT A.*, ROWNUM RN

4 FROM (SELECT ID, OWNER, OBJECT_NAME FROM TEST WHERE OWNER IS NOT NULL ORDER BY OWNER) A

5 WHERE ROWNUM <= 10

6 )

7 WHERE RN >= 1;

 

ID OWNER OBJECT_NAME RN

---------- ------------------------------ ------------------------------ ----------

69170 APEX_030200 WWV_FLOW_INIT_HTP_BUFFER 1

69179 APEX_030200 WWV_HTF 2

69178 APEX_030200 WWV_FLOW_LANG 3

69177 APEX_030200 WWV_FLOW_UTILITIES 4

69176 APEX_030200 VC4000ARRAY 5

69175 APEX_030200 WWV_FLOW_SECURITY 6

69174 APEX_030200 WWV_FLOW 7

69173 APEX_030200 HTMLDB_ITEM 8

69172 APEX_030200 WWV_FLOW_GLOBAL 9

69171 APEX_030200 WWV_FLOW_IMAGE_PREFIX 10

 

10 rows selected.

 

tony@ORCL1> SELECT * FROM

2 (

3 SELECT A.*, ROWNUM RN

4 FROM (SELECT ID, OWNER, OBJECT_NAME FROM TEST WHERE OWNER IS NOT NULL ORDER BY OWNER) A

5 WHERE ROWNUM <= 20

6 )

7 WHERE RN >= 11;

 

ID OWNER OBJECT_NAME RN

---------- ------------------------------ ------------------------------ ----------

69180 APEX_030200 WWV_HTP 11

69179 APEX_030200 WWV_HTF 12

69178 APEX_030200 WWV_FLOW_LANG 13

69177 APEX_030200 WWV_FLOW_UTILITIES 14

69176 APEX_030200 VC4000ARRAY 15

69175 APEX_030200 WWV_FLOW_SECURITY 16

69174 APEX_030200 WWV_FLOW 17

69173 APEX_030200 HTMLDB_ITEM 18

69172 APEX_030200 WWV_FLOW_GLOBAL 19

69171 APEX_030200 WWV_FLOW_IMAGE_PREFIX 20

 

10 rows selected.

--可以看到,有多个ID在两次查询中都出现了。

--通过加上ID作为排序列解决这个问题。

 

tony@ORCL1> SELECT * FROM

2 (

3 SELECT A.*, ROWNUM RN

4 FROM (SELECT ID, OWNER, OBJECT_NAME FROM TEST WHERE OWNER IS NOT NULL ORDER BY OWNER, ID) A

5 WHERE ROWNUM <= 10

6 )

7 WHERE RN >= 1;

 

ID OWNER OBJECT_NAME RN

---------- ------------------------------ ------------------------------ ----------

69170 APEX_030200 WWV_FLOW_INIT_HTP_BUFFER 1

69171 APEX_030200 WWV_FLOW_IMAGE_PREFIX 2

69172 APEX_030200 WWV_FLOW_GLOBAL 3

69173 APEX_030200 HTMLDB_ITEM 4

69174 APEX_030200 WWV_FLOW 5

69175 APEX_030200 WWV_FLOW_SECURITY 6

69176 APEX_030200 VC4000ARRAY 7

69177 APEX_030200 WWV_FLOW_UTILITIES 8

69178 APEX_030200 WWV_FLOW_LANG 9

69179 APEX_030200 WWV_HTF 10

 

10 rows selected.

 

tony@ORCL1> SELECT * FROM

2 (

3 SELECT A.*, ROWNUM RN

4 FROM (SELECT ID, OWNER, OBJECT_NAME FROM TEST WHERE OWNER IS NOT NULL ORDER BY OWNER, ID) A

5 WHERE ROWNUM <= 20

6 )

7 WHERE RN >= 11;

 

ID OWNER OBJECT_NAME RN

---------- ------------------------------ ------------------------------ ----------

69180 APEX_030200 WWV_HTP 11

69181 APEX_030200 ESCAPE_SC 12

69182 APEX_030200 WWV_FLOW_META_DATA 13

69183 APEX_030200 WWV_FLOW_TEMPLATES_UTIL 14

69184 APEX_030200 WWV_RENDER_CALENDAR2 15

69185 APEX_030200 WWV_RENDER_CHART2 16

69186 APEX_030200 WWV_FLOW_CHECK 17

69187 APEX_030200 WWV_RENDER_REPORT3 18

69188 APEX_030200 WWV_FLOW_PAGE_CACHE_API 19

69189 APEX_030200 WWV_FLOW_RENDER_QUERY 20

 

10 rows selected.

posted @ 2018-10-11 09:29  CharyGao  阅读(3686)  评论(0编辑  收藏  举报