本文轉自:http://blog.csdn.net/fw0124/article/details/42737671
**********************************************************************************************************
[轉載]
Oracle的分頁查詢語句基本上可以按照本文給出的格式來進行套用。
Oracle分頁查詢格式(一):http://yangtingkun.itpub.net/post/468/100278
Oracle分頁查詢格式(二):http://yangtingkun.itpub.net/post/468/101703
Oracle分頁查詢格式(三):http://yangtingkun.itpub.net/post/468/104595
Oracle分頁查詢格式(四):http://yangtingkun.itpub.net/post/468/104867
Oracle分頁查詢格式(五):http://yangtingkun.itpub.net/post/468/107934
Oracle分頁查詢格式(六):http://yangtingkun.itpub.net/post/468/108677
Oracle分頁查詢格式(七):http://yangtingkun.itpub.net/post/468/109834
Oracle分頁查詢格式(八):http://yangtingkun.itpub.net/post/468/224557
Oracle分頁查詢格式(九):http://yangtingkun.itpub.net/post/468/224409
Oracle分頁查詢格式(十):http://yangtingkun.itpub.net/post/468/224823
Oracle分頁查詢的排序問題:http://yangtingkun.itpub.net/post/468/112274 Oracle官網連接查詢優化的說明:http://docs.oracle.com/cd/E11882_01/server.112/e16638/optimops.htm#i36235 NESTED LOOP/HASH JOIN/SORT MERGE JOIN的區別:http://jewfinkl.blog.163.com/blog/static/14076982012431052316/
**********************************************************************************************************
根據以上文章進行了如下的總結。
ROWNUM
可能都知道ROWNUM只適用於小於或小於等於,如果進行等於判斷,那么只能等於1,不能進行大於的比較。 ROWNUM是oracle系統順序分配為從查詢返回的行的編號,返回的第一行分配的是1,第二行是2,依此類推。 ROWNUM總是從1開始,不管當前的記錄是否滿足查詢結果,ROWNUM返回的值都是1,如果這條記錄的值最終滿足所有的條件,那么ROWNUM會遞加,下一條記錄的ROWNUM會返回2,否則下一條記錄的ROWNUM仍然返回1。 理解了這一點,就清楚為什么一般的ROWNUM大於某個值或等於某個不為1的值是無法返回結果的,因此對於每條記錄的ROWNUM都是1,而ROWNUM為1不滿足查詢的結果,所以下一條記錄的ROWNUM不會遞增,仍然是1,因此所有的記錄都不滿足條件。
分頁查詢格式1 在查詢的最外層控制分頁的最小值和最大值。查詢語句如下:
- SELECT * FROM
- (
- SELECT A.*, ROWNUM RN
- FROM (SELECT * FROM TABLE_NAME) A
- )
- WHERE RN BETWEEN 21 AND 40
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
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
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,也就是說,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以前,Oracle的GROUP 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
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 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;
--查詢語句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')
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,讀數據發生的IO(consistent 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,排序列就是索引列.注意這里需要加上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) *排序列沒有索引,排序不可避免。帶STOPKEY的ORDER 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.
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.