oracle分页性能不同,oracle高效分页
什么是分頁(yè)查詢
對(duì)于基于Web的應(yīng)用而言,對(duì)查詢的結(jié)果集進(jìn)行分頁(yè)是一個(gè)比較常見的需求。假設(shè)瀏覽器界面每頁(yè)可以顯示10條記錄,最初界面顯示頭10條記錄給用戶,當(dāng)終端用戶點(diǎn)擊“下一頁(yè)”按鈕時(shí),界面顯示接下來(lái)的10條記錄。一般來(lái)說(shuō),Web后臺(tái)服務(wù)程序并不是一次性的把所有符合條件的記錄都返回給瀏覽器,再由瀏覽器應(yīng)用程序?qū)Σ樵兘Y(jié)果進(jìn)行分頁(yè)。現(xiàn)在的普遍做法都是:當(dāng)用戶要瀏覽下一頁(yè)時(shí),瀏覽器重新從WEB后臺(tái)服務(wù)器取出下10條記錄。
對(duì)于采用了數(shù)據(jù)庫(kù)的WEB應(yīng)用來(lái)說(shuō),如何對(duì)查詢的結(jié)果進(jìn)行分頁(yè)就有兩種實(shí)現(xiàn)方式,一種是WEB后臺(tái)程序把全部查詢結(jié)果取到內(nèi)存中,由它實(shí)現(xiàn)分頁(yè)。另一種是每次只從數(shù)據(jù)庫(kù)取出10條記錄,由數(shù)據(jù)庫(kù)實(shí)現(xiàn)分頁(yè)。
這兩種分頁(yè)方式各有優(yōu)缺點(diǎn),有時(shí)可能把這兩種方式結(jié)合起來(lái)應(yīng)用。在這里,我主要介紹一下如何在Oracle數(shù)據(jù)庫(kù)中實(shí)現(xiàn)分頁(yè)查詢。
如何實(shí)現(xiàn)分頁(yè)查詢
認(rèn)識(shí)ROWNUM
Oracle的ROWNUM偽列是實(shí)現(xiàn)結(jié)果集分頁(yè)的關(guān)鍵,可能有很多人對(duì)于ROWNUM偽列到底代表什么還不太清楚,有人甚至認(rèn)為它是數(shù)據(jù)庫(kù)表中記錄的編號(hào)。下面我引用在ASKTOM網(wǎng)站上的他一個(gè)例子幫助大家認(rèn)識(shí)一下ROWNUM到底為何物。為了幫助大家理解,我建了一個(gè)測(cè)試表,然后再插入20條測(cè)試數(shù)據(jù),當(dāng)前原例子中查詢語(yǔ)句表名和字段也做了相應(yīng)的修改。
--建測(cè)試表
createtable t_testrownum
( ridnumber, rvalue varchar2(30))
--插入測(cè)試數(shù)據(jù)
begin
insert into t_testrownum values(1, 'aaaa');
insert into t_testrownum values(2, 'aaaa');
insert into t_testrownum values(3, 'aaaa1');
insert into t_testrownum values(4, 'aaaa');
insert into t_testrownum values(5, 'aaaa');
insert into t_testrownum values(6, 'aaaa');
insert into t_testrownum values(7, 'aaaa');
insert into t_testrownum values(8, 'aaaa4');
insert into t_testrownum values(9, 'aaaa');
insert into t_testrownum values(10, 'aaaa');
insert into t_testrownum values(11, 'aaaa');
insert into t_testrownum values(12, 'aaaa');
insert into t_testrownum values(13, 'aaaa5');
insert into t_testrownum values(14, 'aaaa');
insert into t_testrownum values(15, 'aaaa');
insert into t_testrownum values(16, 'aaaa');
insert into t_testrownum values(17, 'aaaa');
insert into t_testrownum values(18, 'aaaa8');
insert into t_testrownum values(19, 'aaaa');
insert into t_testrownum values(20, 'aaaa');
end;
例1.
select * from t_testrownum where rownum = 1;返回結(jié)果集的第一條記錄。那么select * from T where rownum = 2;應(yīng)該返回結(jié)果集的第二條記錄。可是實(shí)際上第二個(gè)查詢語(yǔ)句不會(huì)返回任何記錄,為什么呢?
類似的例子還有:
select * from T where rownum >= 1 and rownum <= 5;返回前5條記錄,而
select * from T where rownum >= 2 and rownum <= 5;無(wú)記錄返回。
其實(shí):ROWNUM并是記錄編號(hào),而是Oracle在向外輸出結(jié)果集中的記錄時(shí)給它賦的一個(gè)順序號(hào)。當(dāng)不在查詢語(yǔ)句中限制ROWNUM時(shí),其處理邏輯如下所示:
rownum= 1
forx in ( select * from T )
loop
if ( x satisifies the predicate )
then
OUTPUT the row
rownum = rownum + 1
end if;
endloop;
當(dāng)限制ROWNUM時(shí),我們對(duì)比一下下面兩個(gè)查詢的執(zhí)行計(jì)劃:
語(yǔ)句1:select* from t_testrownum;
語(yǔ)句1的執(zhí)行計(jì)劃:
SELECT STATEMENT Optimizer Mode=CHOOSE TABLE
ACCESS FULL USDPD502.T_TESTROWNUM
語(yǔ)句2:select* from t_testrownum where rownum <= 10;
語(yǔ)句2的執(zhí)行計(jì)劃
SELECT STATEMENT Optimizer Mode=CHOOSE
COUNT STOPKEY
TABLE ACCESS FULL USDPD502.T_TESTROWNUM
通過(guò)對(duì)比,我們可以看出語(yǔ)句2的執(zhí)行計(jì)劃中增加了一條‘COUNT STOPKEY’,該句的意思是當(dāng)rownum已超出指定范圍時(shí),停止輸出,其處理邏輯如下:
rownum= 1
forx in ( select * from T )
loop
if( x satisifies the predicate )
then
OUTPUT the row
rownum= rownum + 1
endif;
if ( rownum已超出指定范圍)
then
跳出循環(huán)
endif;
endloop;
至此,我們就可以解釋上面兩個(gè)例子中的問(wèn)題了。當(dāng)我們限制rownum=1時(shí),第一條記錄滿足該條件,輸出該記錄,rownum增1,由于rownum已超出范圍,停止輸出。當(dāng)我們限制rownum=2時(shí),由于第一條記錄不滿足條件,不輸出該記錄,rownum也不增加。接著取第二條記錄,由于rownum此時(shí)還是1,不滿足條件,同樣也不輸出,如此直到遍歷全部記錄結(jié)束循環(huán)。
基本的分頁(yè)查詢
當(dāng)知道rownum是怎么回事后,我們就可以利用它來(lái)實(shí)現(xiàn)分頁(yè)查詢了。假如我們想從表T中取出第11條到第20條記錄,在未透徹了解ROWNUM之前,許多人可能會(huì)寫出下面的查詢語(yǔ)句。
--語(yǔ)句1
select* t_testrownum a where rownum >= 11 and rownum <= 20;
通過(guò)前面的分析,我們知道,這樣的寫法是錯(cuò)誤的。所以,我們把它修改為如下的寫法。
--語(yǔ)句2
select*
from( select a.*, rownum r from t_testrownum a )
wherer>= 11 and r <= 20
該語(yǔ)句的輸出結(jié)果是正確的,它的內(nèi)層查詢先從表t_testrownum中查詢出所有記錄,同時(shí)為每條記錄賦一個(gè)順序編號(hào)r,外層查詢?cè)傧拗浦贿x取編號(hào)為11到20之間的記錄。
從查詢效率上考慮一下,如果我們只需要得到第11到20條之間的記錄,那么在內(nèi)層查詢中就可以利用rownum限制內(nèi)層查詢輸出的記錄數(shù)。修改后的語(yǔ)句如下:
--語(yǔ)句3
select*
from( select a.*, rownum r
fromt_testrownum a where rownum <= 20)
wherer>= 11
需要排序的分頁(yè)查詢
有人會(huì)想,排序那還不簡(jiǎn)單嗎,加上order by子句就行了。
--語(yǔ)句4
select*
from( select a.*, rownum r from t_testrownum a where rownum <= 20 order by rvalue)
wherer>= 11
我們都知道order by是對(duì)輸出的結(jié)果集進(jìn)行排序,而不是先排序然后輸出結(jié)果集。語(yǔ)句4的實(shí)際效果是,從表t_testrownum中取出前20行記錄,然后按照rvalue字段排序,輸出排序編號(hào)大于等于11的記錄。
只對(duì)前20條記錄進(jìn)行排序顯然不是我們所期望的,為避免這個(gè)問(wèn)題,有人可能會(huì)把上面的語(yǔ)句做如下修改:
--語(yǔ)句5
select*
from( select a.*, rownum r from t_testrownum a order by rvalue)
wherer>= 11 and r <= 20
同樣,由于rownum在排序之前就確定了,我們得到得記錄并不是排序后的第11到20條記錄,而是排序前的第11到20條記錄。為得到我們期望的結(jié)果,我們必須把rownum r放到order by的外面。修改后的查詢語(yǔ)句如下。
--語(yǔ)句6
select*
from( select b.*, rownum r
from( select a.*
fromt_testrownum a order by rvalue ) b
where rownum <= 20 )
wherer>= 11
如果排序字段rvalue的值在表t_testrownum中是唯一的,那么上面的語(yǔ)句從功能實(shí)現(xiàn)上來(lái)說(shuō),就沒(méi)什么問(wèn)題了。但是如果rvalue字段的值不唯一,假設(shè)按rvalue排序后,前1到20條記錄的rvalue字段的值是相同的,我們先查出第1到10條記錄,然后再查出第11到20條記錄,這是我們會(huì)發(fā)現(xiàn),同一條記錄可能同時(shí)出現(xiàn)在這兩個(gè)查詢結(jié)果集中。這是為什么呢。一開始,我認(rèn)為是Oracle采用的排序算法是不穩(wěn)定的,兩個(gè)相同的值在兩次排序中的順序是不固定的。但是我們把語(yǔ)句select a.* from t_testrownum a order by rvalue執(zhí)行10次,卻發(fā)現(xiàn)輸出結(jié)果集的排序順序都是一致的。那么是什么導(dǎo)致排序不一致呢。為此,我們觀察了一下語(yǔ)句6的執(zhí)行計(jì)劃:
SELECT STATEMENT, GOAL = CHOOSE
VIEWObject owner=USDPD502
COUNT STOPKEY
VIEW Object owner=USDPD502
SORT ORDER BY STOPKEY
TABLE ACCESS FULL Objectowner=USDPD502 Objectname=T_TESTROWNUM
從執(zhí)行計(jì)劃中我們看出,執(zhí)行計(jì)劃的第2步是“SORT ORDER BY STOPKEY”,它表示
其并不是對(duì)所有符合條件的記錄完全排序,而是僅僅找到符合排序條件的指定條數(shù)的記錄,比如我們限制rownum <= 20,則只需找到排序在前20位的記錄。記得在Oracle的官方文檔上我曾經(jīng)見到Oracle聲稱其排序是穩(wěn)定的一致的。前面我們將select a.* from t_testrownum a order by rvalue執(zhí)行10次,發(fā)現(xiàn)排序是一致的。那么“SORT ORDER BY STOPKEY”方式的排序是否是一致的呢?我們將語(yǔ)句6執(zhí)行10次同樣發(fā)現(xiàn),其結(jié)果是一致的。那么為什么我們用語(yǔ)句6查第1到10條記錄和11到20條記錄時(shí),有些記錄為什么在這兩個(gè)查詢中出現(xiàn)的名次并不一致呢。
--語(yǔ)句7
select*
from( select a.* from t_testrownum a order by rvalue )
where rownum <= 20
--語(yǔ)句8
select*
from( select a.* from t_testrownum a order by rvalue )
where rownum <= 10
為了找出排序不一致的原因,我們分別執(zhí)行語(yǔ)句7和語(yǔ)句8,這時(shí)你會(huì)發(fā)現(xiàn),前10名的記錄在兩次查詢中并不一樣。為此,我們得出結(jié)論,當(dāng)stopkey不同時(shí),排序結(jié)果是不同的。為什么會(huì)這樣呢,大師TOM的解釋是“SORT ORDER BY STOPKEY”是Oracle為優(yōu)化TOPN(查詢排序后的前N條記錄)查詢采用的一種算法。大致的思想是:先取出為排序時(shí)前面的N條記錄,對(duì)這N條記錄排序,然后用后面的剩下的所有記錄依排序要求插入前N條記錄中。一般來(lái)說(shuō),這樣的插入排序也應(yīng)該是穩(wěn)定的,那為什么N不同,排序結(jié)果就不一樣呢?下面的兩條查詢語(yǔ)句似乎可以給你一點(diǎn)啟發(fā):
--語(yǔ)句9
selecta.*, rownum r
fromt_testrownum a
whererownum <= 10
orderby rvalue
--語(yǔ)句10
select a.*, rownum r
fromt_testrownum a
whererownum <= 20
orderby rvalue
它們的執(zhí)行計(jì)劃如下:
SELECT STATEMENT, GOAL = CHOOSE
SORT ORDER BY
COUNT STOPKEY
TABLE ACCESS FULL Object owner=USDPD502 Object name=T_TESTROWNUM
從上面兩條查詢語(yǔ)句的結(jié)果我們可以看出,排在前面的10記錄也不是一致的。要注意的是,這兩條語(yǔ)句的執(zhí)行計(jì)劃中并沒(méi)有使用“SORT ORDER BY STOPKEY”算法。而是普通的排序“SORT ORDER BY”。只是這兩次排序的記錄條數(shù)不一樣,這時(shí)有些人可能會(huì)懷疑是在排序前兩次查詢輸出記錄的順序就是不一樣的。我們可以這么測(cè)試一下,先對(duì)t_testrownum表的所有20條記錄排序,然后從表中刪除掉后10條記錄,從語(yǔ)句9和語(yǔ)句10中刪除掉where rownum <= N條件,我們發(fā)現(xiàn),查詢結(jié)果和語(yǔ)句9和語(yǔ)句10是一樣的。由此,我們可以得出結(jié)論,當(dāng)參與排序的記錄數(shù)量不同時(shí),具有相同值的記錄的排序順序是不同的。
進(jìn)行分頁(yè)查詢時(shí),如果同一條記錄在多個(gè)分頁(yè)中出現(xiàn),這樣的結(jié)果肯定不是你所期望的。為了避免這種現(xiàn)象的發(fā)生,一個(gè)簡(jiǎn)單的方法就是在排序條件中增加輔助排序字段,使得每條記錄的組合排序字段是唯一的。
如何在分頁(yè)查詢中避免排序
對(duì)于需要排序的分頁(yè)查詢來(lái)說(shuō),如果參與排序的結(jié)果集很大,而實(shí)際返回的記錄數(shù)很少,那么有兩點(diǎn)是需要注意的:第一大結(jié)果集排序?qū)ο到y(tǒng)資源的占用,第二如果排序字段的值不唯一,某些記錄會(huì)出現(xiàn)在多個(gè)分頁(yè)中。如何避免以上的兩點(diǎn)呢,我們知道,索引的鍵值是有序組織的,我們是否可以利用索引來(lái)避免排序呢。答案是肯定的,我們?cè)趓value上建立索引:
--語(yǔ)句11
createindex idx_testrownum_rvalue on t_testrownum(rvalue);
這時(shí),我們把語(yǔ)句6修改一下:
--語(yǔ)句12
select*
from( select b.*, rownum r
from( select a.*
fromt_testrownum a
wherervalue > chr(1) order by rvalue ) b
where rownum <= 20 )
wherer>= 11
執(zhí)行語(yǔ)句12,它的執(zhí)行計(jì)劃如下:
SELECT STATEMENT, GOAL = CHOOSE
VIEW Object owner=USDPD502
COUNT STOPKEY
VIEW Object owner=USDPD502
TABLE ACCESS BY INDEX ROWID Object owner=USDPD502 Object name=T_TESTROWNUM
INDEX RANGE SCAN Object owner=USDPD502 Object name=IDX_TESTROWNUM_RVALUE
從上面的執(zhí)行計(jì)劃中,我們已看不到“SORT ORDER BY STOPKEY”字樣,說(shuō)明沒(méi)有排序步驟。那么記錄在分頁(yè)中重復(fù)的問(wèn)題是否也解決了呢,經(jīng)過(guò)測(cè)試,該問(wèn)題也不復(fù)存在。
那么如果我們需要降序排序呢?對(duì)于降序排序,我們需要增加相應(yīng)的hints來(lái)提示優(yōu)化器走降序索引掃描。
-語(yǔ)句13
select *
from( select b.*, rownum r
from( select/*+index_desc(a idx_testrownum_rvalue)*/a.*
fromt_testrownum a
wherervalue > chr(1) order by rvalue desc ) b
where rownum <= 20 )
wherer>= 11
語(yǔ)句13的執(zhí)行計(jì)劃如下:
SELECT STATEMENT, GOAL = CHOOSE
VIEW Object owner=USDPD502
COUNT STOPKEY
VIEW Object owner=USDPD502
TABLE ACCESS BY INDEX ROWID Object owner=USDPD502 Object name=T_TESTROWNUM
INDEXRANGESCAN DESCENDING Objectowner=USDPD502
利用索引避免排序需要的注意點(diǎn)
雖然使用索引來(lái)避免排序是一個(gè)好方法,但是,任何事物都不可能是十全十美的,使用該方法時(shí)需要注意以下幾點(diǎn):
1)排序字段上的索引必須是升序索引,如果使用降序索引將導(dǎo)致升序排序時(shí)分頁(yè)出現(xiàn)問(wèn)題。(具體是什么原因我現(xiàn)在還沒(méi)弄明白,如果有知道原因的可以指點(diǎn)一下)
2)在分頁(yè)開始記錄數(shù)大于10000后,利用索引排序進(jìn)行分頁(yè)的性能反而不如直接排序分頁(yè)的方式好。(如果字段是數(shù)字性,性能下降不大,如果是字符串則性能下降明顯,這可能和字符串和數(shù)字的比較方式不同有關(guān))
總結(jié)
以上是生活随笔為你收集整理的oracle分页性能不同,oracle高效分页的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: php提示行号,在php中使用trigg
- 下一篇: oracle 如何添加数据文件,Orac