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

 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 Loops,Hash 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, JOBS为inner table.
二. HASH JOIN :
散列连接是CBO 做大数据集连接时的常用方式,优化器使用两个表中较小的表(或数据源)利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。
这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和。但是在表很大的情况下并不能完全放入内存,这时优化器会将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要有较大的临时段从而尽量提高I/O 的性能。
也可以用USE_HASH(table_name1 table_name2)提示来强制使用散列连接。如果使用散列连接HASH_AREA_SIZE 初始化参数必须足够的大,如果是9i,Oracle建议使用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 TABLE,ORDER_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

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

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

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

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 LOOP和HASH JOIN(MERGE 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 LOOP比HASH 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

行数据,表格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;

 

----------------------------------------------------------
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):
---------------------------------------------------

----------------------------------------------------------------------------
| 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的第一步即使对T进行全表扫描而无法应用stopkey,效率也很高。

-----------------------------------------------------------------------------
| 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):
---------------------------------------------------

-----------------------------------------------------------------------------------------------
| 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')

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

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

,排序列就是索引列.注意这里需要加上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;

 

,排序列没有索引

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T1 ORDER
BY
NAME) A

WHERE
ROWNUM <= 40

)

WHERE RN >= 21;

 

 

,排序列没有索引

SELECT * FROM

(

SELECT A.*, ROWNUM RN

FROM (SELECT * FROM T1 ORDER
BY
NAME) A

)

WHERE RN BETWEEN
21
AND
40;

 
 

-----------------------------------------------------------------------------------------------
| 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)

-----------------------------------------------------------------------------------------
| 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)

--------------------------------------------------------------------------------------
| 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.

Oracle ROWNUM用法和分页查询总结的更多相关文章

  1. Oracle ROWNUM用法和分页查询总结(转)

    [转载] Oracle的分页查询语句基本上可以按照本文给出的格式来进行套用. Oracle分页查询格式(一):http://yangtingkun.itpub.net/post/468/100278 ...

  2. [转]Oracle ROWNUM用法和分页查询总结

    本文转自:http://blog.csdn.net/fw0124/article/details/42737671 ****************************************** ...

  3. oracle count 百万级 分页查询记要总数、总条数优化

    oracle count 百万级 分页查询记录总数.总条数优化 oracle count 百万级 查询记录总数.总条数优化 最近做一个项目时,做分页时,发现分页查询速度很慢,分页我做的是两次查询,一次 ...

  4. Oracle和MySql的分页查询区别和PL/SQL的基本概念

    Oracle和MySql的分页查询区别:     Oracle的分析查询,之前Oracle的分页是使用伪列 ROWNUM 结合子查询实现,mysql的分页更简单,直接使用 LIMIT 关键字就可以实现 ...

  5. Oracle中的SQL分页查询原理和方法详解

    Oracle中的SQL分页查询原理和方法详解 分析得不错! http://blog.csdn.net/anxpp/article/details/51534006

  6. MySQL、SqlServer、Oracle三大主流数据库分页查询

    在这里主要讲解一下MySQL.SQLServer2000(及SQLServer2005)和ORCALE三种数据库实现分页查询的方法.可能会有人说这些网上都有,但我的主要目的是把这些知识通过我实际的应用 ...

  7. 详解Oracle的几种分页查询语句

    转载自:http://database.51cto.com/art/200904/118737.htm 分页查询格式: SELECT * FROM (SELECT A.*, ROWNUM RN FRO ...

  8. Oracle/MySql/SQL Sqlserver分页查询

    简述 简单概括一下Oracle,MySql,SQL Sqlserver这三个数据库的分页查询语句. Oracle分页查询 例:每页显示两条数据,现在要查询第二页,也就是第3-4条数据. 查询语句: s ...

  9. 使用Oracle实现的MyBatis分页查询效果

    1.mybatis.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configur ...

随机推荐

  1. golang 类型转换

    import ( "bytes" "encoding/binary" "encoding/gob" "fmt" ) fu ...

  2. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  3. 在 java 开发接口中需要注意的问题

    1 在开发过程中免不了对接上游或下游,有合作就要保证入参.出参的准确性.一个接口一般只能处理有限情况下的情况,因此在逻辑处理前要对入参进行校验. 2 在自己的逻辑处理过程中,要时刻持有怀疑的态度.假设 ...

  4. mschart asp chart 用法,包括前台写法与后台写法,还有click事件,如何触发。

    纯后台动态生成aspchart ,这种方式没办法实现chart中click事件.click事件点击没有反应,用第二种可以实现点击事件. 两种方式实现同一种效果图: 第一种写法:后台动态生成aspcha ...

  5. svg中实现文字随曲线走向,HTML直接写和JavaScript创建对象两种方式

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat=&qu ...

  6. Kafka原理总结

    Kafka Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实 ...

  7. JSP之开发环境搭建

    1.下载JDK1.8(或JDK1.7),并进行安装和配置,主要是配置环境变量JAVA_HOME及Path. 2.下载并配置Tomcat8.0(或Tomcat7.0). Windows平台请下载Tomc ...

  8. AssetManager

    AssetManager用于获取assets下的资源. 1.getassets()得到AssetManager 2.AssetManager.close() 关闭AssetManager 3.Reso ...

  9. 24小时学通Linux内核之调度和内核同步

    心情大好,昨晚我们实验室老大和我们聊了好久,作为已经在实验室待了快两年的大三工科男来说,老师让我们不要成为那种技术狗,代码工,说多了都是泪啊,,不过我们的激情依旧不变,老师帮我们组好了队伍,着手参加明 ...

  10. 图像的下采样Subsampling 与 上采样 Upsampling

     I.目的 缩小图像(或称为下采样(subsampled)或降采样(downsampled))的主要目的: 1.使得图像符合显示区域的大小: 2.生成对应图像的缩略图. 放大图像(或称为上采样(ups ...