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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM