Oracle的分頁查詢語句基本上可以按照本文給出的格式來進(jìn)行套用。
(一) 分頁查詢格式: SELECT * FROM 其中最內(nèi)層的查詢SELECT * FROM TABLE_NAME表示不進(jìn)行翻頁的原始查詢語句。ROWNUM <= 40和RN >= 21控制分頁查詢的每頁的范圍。 上面給出的這個(gè)分頁查詢語句,在大多數(shù)情況擁有較高的效率。分頁的目的就是控制輸出結(jié)果集大小,將結(jié)果盡快的返回。在上面的分頁查詢語句中,這種考慮主要體現(xiàn)在WHERE ROWNUM <= 40這句上。 選擇第21到40條記錄存在兩種方法,一種是上面例子中展示的在查詢的第二層通過ROWNUM <= 40來控制最大值,在查詢的最外層控制最小值。而另一種方式是去掉查詢第二層的WHERE ROWNUM <= 40語句,在查詢的最外層控制分頁的最小值和最大值。這是,查詢語句如下: SELECT * FROM 對比這兩種寫法,絕大多數(shù)的情況下,第一個(gè)查詢的效率比第二個(gè)高得多。 這是由于CBO優(yōu)化模式下,Oracle可以將外層的查詢條件推到內(nèi)層查詢中,以提高內(nèi)層查詢的執(zhí)行效率。對于第一個(gè)查詢語句,第二層的查詢條件WHERE ROWNUM <= 40就可以被Oracle推入到內(nèi)層查詢中,這樣Oracle查詢的結(jié)果一旦超過了ROWNUM限制條件,就終止查詢將結(jié)果返回了。 而第二個(gè)查詢語句,由于查詢條件BETWEEN 21 AND 40是存在于查詢的第三層,而Oracle無法將第三層的查詢條件推到最內(nèi)層(即使推到最內(nèi)層也沒有意義,因?yàn)樽顑?nèi)層查詢不知道RN代表什么)。因此,對于第二個(gè)查詢語句,Oracle最內(nèi)層返回給中間層的是所有滿足條件的數(shù)據(jù),而中間層返回給最外層的也是所有數(shù)據(jù)。數(shù)據(jù)的過濾在最外層完成,顯然這個(gè)效率要比第一個(gè)查詢低得多。 上面分析的查詢不僅僅是針對單表的簡單查詢,對于最內(nèi)層查詢是復(fù)雜的多表聯(lián)合查詢或最內(nèi)層查詢包含排序的情況一樣有效。 這里就不對包含排序的查詢進(jìn)行說明了,下一篇文章會(huì)通過例子來詳細(xì)說明。下面簡單討論一下多表聯(lián)合的情況。對于最常見的等值表連接查詢,CBO一般可能會(huì)采用兩種連接方式NESTED LOOP和HASH JOIN(MERGE JOIN效率比HASH JOIN效率低,一般CBO不會(huì)考慮)。在這里,由于使用了分頁,因此指定了一個(gè)返回的最大記錄數(shù),NESTED LOOP在返回記錄數(shù)超過最大值時(shí)可以馬上停止并將結(jié)果返回給中間層,而HASH JOIN必須處理完所有結(jié)果集(MERGE JOIN也是)。那么在大部分的情況下,對于分頁查詢選擇NESTED LOOP作為查詢的連接方法具有較高的效率(分頁查詢的時(shí)候絕大部分的情況是查詢前幾頁的數(shù)據(jù),越靠后面的頁數(shù)訪問幾率越小)。 因此,如果不介意在系統(tǒng)中使用HINT的話,可以將分頁的查詢語句改寫為: SELECT /*+ FIRST_ROWS */ * FROM
(二)
這篇文章用幾個(gè)例子來說明分頁查詢的效率。首先構(gòu)造一個(gè)比較大的表作為測試表: SQL> CREATE TABLE T AS SELECT * FROM DBA_OBJECTS, DBA_SEQUENCES; 表已創(chuàng)建。 SQL> SELECT COUNT(*) FROM T; COUNT(*) 首先比較兩種分頁方法的區(qū)別: SQL> SET AUTOT ON PL/SQL 過程已成功完成。 SQL> SELECT OBJECT_ID, OBJECT_NAME OBJECT_ID OBJECT_NAME 已選擇10行。
Statistics SQL> SELECT OBJECT_ID, OBJECT_NAME OBJECT_ID OBJECT_NAME 已選擇10行。
Statistics 二者執(zhí)行效率相差很大,一個(gè)需要8000多邏輯讀,而另一個(gè)只需要5個(gè)邏輯讀。觀察二者的執(zhí)行計(jì)劃可以發(fā)現(xiàn),兩個(gè)執(zhí)行計(jì)劃唯一的區(qū)別就是第二個(gè)查詢在COUNT這步使用了STOPKEY,也就是說,Oracle將ROWNUM <= 20推入到查詢內(nèi)層,當(dāng)符合查詢的條件的記錄達(dá)到STOPKEY的值,則Oracle結(jié)束查詢。 因此,可以預(yù)見,采用第二種方式,在翻頁的開始部分查詢速度很快,越到后面,效率越低,當(dāng)翻到最后一頁,效率應(yīng)該和第一種方式接近。 SQL> SELECT OBJECT_ID, OBJECT_NAME OBJECT_ID OBJECT_NAME 已選擇11行。
Statistics
---------------------------------------------------------- 0 recursive calls 0 db block gets 8979 consistent gets 7423 physical reads 0 redo size 680 bytes sent via SQL*Net to client 503 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 11 rows processed (三)
繼續(xù)看查詢的第二種情況,包含表連接的情況: SQL> CREATE TABLE T AS SELECT * FROM DBA_USERS; 表已創(chuàng)建。 SQL> CREATE TABLE T1 AS SELECT * FROM DBA_SOURCE; 表已創(chuàng)建。 SQL> ALTER TABLE T ADD CONSTRAINT PK_T PRIMARY KEY (USERNAME); 表已更改。 SQL> ALTER TABLE T1 ADD CONSTRAINT FK_T1_OWNER FOREIGN KEY (OWNER) 表已更改。 SQL> CREATE INDEX IND_T1_OWNER ON T1(NAME); 索引已創(chuàng)建。 SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, ‘T‘) PL/SQL 過程已成功完成。 SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, ‘T1‘) PL/SQL 過程已成功完成。 創(chuàng)建了T表和T1表,默認(rèn)情況下,HASH JOIN的效率要比NESTED LOOP高很多: SQL> SET AUTOT TRACE 已選擇96985行。
Statistics SQL> SELECT /*+ FIRST_ROWS */ * FROM T, T1 WHERE T.USERNAME = T1.OWNER; 已選擇96985行。
但是如果分頁查詢的內(nèi)層是這種連接查詢的話,使用NESTED LOOP可以更快的得到前N條記錄。 下面看一下這種情況下的分頁查詢情況: SQL> SELECT USER_ID, USERNAME, NAME 已選擇10行。
Statistics SQL> SELECT /*+ FIRST_ROWS */ USER_ID, USERNAME, NAME 已選擇10行。
看上去似乎HASH JOIN效率更高,難道上面說錯(cuò)了。 其實(shí)這個(gè)現(xiàn)象是由于這個(gè)例子的特殊性造成的。T表是根據(jù)DBA_USERS創(chuàng)建,這張表很小。HASH JOIN中第一步也就是第一張表的全表掃描是無法應(yīng)用STOPKEY的,這就是上面提到的NESTED LOOP比HASH JOIN優(yōu)勢的地方。但是,這個(gè)例子中,恰好第一張表很小,對這張表的全掃描的代價(jià)極低,因此,顯得HASH JOIN效率更高。但是,這不具備共性,如果兩張表的大小相近,或者Oracle錯(cuò)誤的選擇了先掃描大表,則使用HASH JOIN的效率就會(huì)低得多。 SQL> SELECT USER_ID, USERNAME, NAME 已選擇10行。
Statistics 通過HINT提示,讓Oracle先掃描大表,這回結(jié)果就很明顯了。NESTED LOOP的效果要比HASH JOIN好得多。 下面,繼續(xù)比較一下兩個(gè)分頁操作的寫法,為了使結(jié)果更具有代表性,這里都采用了FIRST_ROWS提示,讓Oracle采用NESTED LOOP的方式來進(jìn)行表連接: SQL> SELECT /*+ FIRST_ROWS */ USER_ID, USERNAME, NAME 已選擇10行。
SQL> SELECT /*+ FIRST_ROWS */ USER_ID, USERNAME, NAME 已選擇10行。
兩種寫法的效率差別極大。關(guān)鍵仍然是是否能將STOPKEY應(yīng)用到最內(nèi)層查詢中。 對于表連接來說,在寫分頁查詢的時(shí)候,可以考慮增加FIRST_ROWS提示,它有助于更快的將查詢結(jié)果返回。 其實(shí),不光是表連接,對于所有的分頁查詢都可以加上FIRST_ROWS提示。不過需要注意的時(shí),分頁查詢的目標(biāo)是盡快的返回前N條記錄,因此,無論是ROWNUM還是FIRST_ROWS機(jī)制都是提高前幾頁的查詢速度,對于分頁查詢的最后幾頁,采用這些機(jī)制不但無法提高查詢速度,反而會(huì)明顯降低查詢效率,對于這一點(diǎn)使用者應(yīng)該做到心中有數(shù)。 |
|