如何處理大數(shù)據(jù)量的查詢
在實(shí)際的任何一個(gè)系統(tǒng)中,查詢都是必不可少的一個(gè)功能,而查詢?cè)O(shè)計(jì)的好壞又影響到系統(tǒng)的響應(yīng)時(shí)間和性能這兩個(gè)關(guān)鍵指標(biāo),尤其是當(dāng)數(shù)據(jù)量變得越來(lái)越大時(shí),于是數(shù)據(jù)分析師如何處理大數(shù)據(jù)量的查詢成了每個(gè)系統(tǒng)架構(gòu)設(shè)計(jì)時(shí)都必須面對(duì)的問(wèn)題。本文將從數(shù)據(jù)及數(shù)據(jù)查詢的特點(diǎn)分析出發(fā),結(jié)合討論現(xiàn)有各種解決方案的優(yōu)缺點(diǎn)及其適用范圍,來(lái)闡述J2EE平臺(tái)下如何進(jìn)行查詢框架的設(shè)計(jì)。
Value List Handler模式及其局限性
在J2EE應(yīng)用中,對(duì)于大數(shù)據(jù)量查詢的處理有許多好的成功經(jīng)驗(yàn),比如Value List Handler設(shè)計(jì)模式就是其中非常經(jīng)典的一個(gè),見(jiàn)圖1。該模式創(chuàng)建一個(gè)ValueListHandler對(duì)象來(lái)控制查詢的執(zhí)行以及結(jié)果集的緩存,它通過(guò)DAO(Data Access Object)來(lái)執(zhí)行查詢,并將數(shù)據(jù)庫(kù)返回的結(jié)果集(傳輸對(duì)象Transfer Object的集合)緩存起來(lái),接下來(lái)的客戶端查詢請(qǐng)求將直接從緩存中獲得。它的特點(diǎn)主要體現(xiàn)在兩點(diǎn):服務(wù)器端緩存數(shù)據(jù),每次只返回客戶端本次操作所需的數(shù)據(jù),通過(guò)這兩個(gè)措施來(lái)減少數(shù)據(jù)庫(kù)的訪問(wèn)次數(shù)以及增加客戶端的響應(yīng)速度,達(dá)到最優(yōu)的查詢效果。當(dāng)然,這里面隱含一個(gè)前提就是客戶端采用分頁(yè)的方式來(lái)瀏覽數(shù)據(jù)。關(guān)于該模式的詳細(xì)介紹,請(qǐng)參考[Core J2EE Patterns]一書(shū)。
圖1:Value List Handler類圖
但是在實(shí)際的應(yīng)用過(guò)程中,會(huì)發(fā)現(xiàn)該模式存在一定的局限性,其實(shí)可以說(shuō)是該模式應(yīng)用具有一些前提條件:
1、由于緩存是以內(nèi)存來(lái)?yè)Q性能,這對(duì)于小數(shù)據(jù)量會(huì)工作得很好,但是如果結(jié)果集很大,內(nèi)存消耗將會(huì)非常嚴(yán)重。同時(shí),消耗在處理結(jié)果集上的時(shí)間也會(huì)越來(lái)越長(zhǎng),比如要循環(huán)讀取記錄集中的數(shù)據(jù),然后依次填充每個(gè)傳輸對(duì)象,想想看幾百萬(wàn)條數(shù)據(jù)這樣處理起來(lái)肯定讓人不能忍受。過(guò)長(zhǎng)的處理時(shí)間不僅降低反應(yīng)速度,同時(shí)還會(huì)占用寶貴的數(shù)據(jù)庫(kù)連接資源,造成其它地方無(wú)連接可用。雖然,在DAO模式中利用CachedRowSet,Read Only RowSet ,RowSet Wrapper List等策略(詳見(jiàn)參考資料)來(lái)代替Transfer Object Collection策略,有效地提高了處理速度,但是仍然存在著在大集合數(shù)據(jù)中進(jìn)行定位、遍歷等問(wèn)題。試想一想,即使在CachedRowSet中的absolute(2000000)也是非常費(fèi)時(shí)的操作。所有這一切的根源就在于緩存是一次性讀取所有的數(shù)據(jù),雖然有時(shí)你可以利用業(yè)務(wù)邏輯來(lái)強(qiáng)制性增加一些限制條件(比如產(chǎn)品查詢必須選擇大類和次類),但這種限制往往是不牢靠的或者說(shuō)只是一時(shí)的權(quán)宜之計(jì)。也有人提出,可以不必緩存所有的查詢結(jié)果,而采取只緩存部分結(jié)果集,比如500,1000條,但這樣一來(lái),就涉及到復(fù)雜的查詢數(shù)據(jù)是否越界的控制,增加了復(fù)雜度,同時(shí)也不易實(shí)現(xiàn)。
2、既然使用緩存,那就不得不面對(duì)一個(gè)數(shù)據(jù)更新的問(wèn)題,使用緩存,實(shí)際上就假定了在數(shù)據(jù)緩存期間,數(shù)據(jù)庫(kù)中的數(shù)據(jù)不會(huì)改變,或者這些改變可以不被反映出來(lái)。但是,在很多場(chǎng)合下(比如常見(jiàn)的業(yè)務(wù)系統(tǒng)中)這些數(shù)據(jù)庫(kù)中的數(shù)據(jù)經(jīng)常會(huì)發(fā)生變化,而且這些改變需要及時(shí)反映給客戶端。
3、緩存其實(shí)存在一個(gè)基本前提,就是緩存的數(shù)據(jù)會(huì)被客戶端反復(fù)查詢使用,具體到分頁(yè)查詢就是客戶會(huì)選擇不同的頁(yè)數(shù)來(lái)查看數(shù)據(jù)。如果客戶端的查詢條件始終變化,或者用戶基本上只關(guān)心第一頁(yè)的數(shù)據(jù)(仔細(xì)琢磨一下用戶的習(xí)慣,這在很多中應(yīng)用場(chǎng)合都很常見(jiàn)),那緩存就失去了應(yīng)有的意義,變得多此一舉了。
數(shù)據(jù)分析
所以說(shuō),在決定是否應(yīng)用某種設(shè)計(jì)模式前,我們需要對(duì)被查詢數(shù)據(jù)的特點(diǎn)以及這些數(shù)據(jù)以何種方式被使用(查詢的特點(diǎn))進(jìn)行一個(gè)分析,根據(jù)不同的結(jié)論來(lái)決定采用何種處理策略。而且,數(shù)據(jù)本身的特點(diǎn)和被使用的方式往往交織在一起,需要綜合起來(lái)考慮,但這其中主要的考量點(diǎn)還是數(shù)據(jù)查詢的特點(diǎn)。
一般來(lái)說(shuō),可以從以下幾個(gè)方面來(lái)分析數(shù)據(jù):
1、 數(shù)據(jù)量大。
這是我們今天討論的數(shù)據(jù)的一個(gè)最基本特點(diǎn),這個(gè)特點(diǎn)在查詢框架設(shè)計(jì)時(shí)要引起足夠的重視。
注意:大數(shù)據(jù)量的查詢是指查詢時(shí)匹配條件的數(shù)據(jù)量大,而不是指表中的數(shù)據(jù)量大,雖然大部分時(shí)候這兩者都是一致的。因?yàn)樵谀承┣闆r下,業(yè)務(wù)邏輯可以限制或者只需要一次獲取很少量的數(shù)據(jù),而查詢的表中的數(shù)據(jù)量卻可能很大,那這種情況就不屬于本文的討論范圍。
2、 關(guān)聯(lián)復(fù)雜,多表關(guān)聯(lián)。
越是簡(jiǎn)單的數(shù)據(jù)可能關(guān)聯(lián)越少,而越是復(fù)雜的數(shù)據(jù)往往都是多表關(guān)聯(lián),這樣很多時(shí)候你(數(shù)據(jù)分析師)需要將這幾張表作為一個(gè)整體來(lái)考慮。
3、 變化頻率。
從這個(gè)角度出發(fā),可以大致將數(shù)據(jù)分為以下幾類:幾乎不變化的睡眠數(shù)據(jù);有規(guī)律定時(shí)更新的數(shù)據(jù),比如招聘網(wǎng)站的職位信息;經(jīng)常性無(wú)規(guī)律更新的數(shù)據(jù)。
4、 成長(zhǎng)性。
數(shù)據(jù)是否具有成長(zhǎng)性,要預(yù)見(jiàn)數(shù)據(jù)的成長(zhǎng)性,并在現(xiàn)有方案中考慮這種成長(zhǎng)性,避免到時(shí)候查詢框架的重新設(shè)計(jì),象大部分的業(yè)務(wù)數(shù)據(jù)都具有這種成長(zhǎng)性。
注意:這里也要特別注意區(qū)分?jǐn)?shù)據(jù)本身的成長(zhǎng)性和數(shù)據(jù)查詢的成長(zhǎng)性,這看似等同的兩者其實(shí)還是存在很大的區(qū)別。就拿招聘網(wǎng)站來(lái)說(shuō),有效職位的數(shù)據(jù)肯定是一天天在增加,具有高成長(zhǎng)性,但是在某個(gè)區(qū)間(比如一個(gè)月,一個(gè)星期)內(nèi)的有效職位查詢則變化不會(huì)太大,不具有成長(zhǎng)性。而后者卻往往是實(shí)際系統(tǒng)中最常遇到的查詢情況。
5、 數(shù)據(jù)查詢的頻率和方式。
所有的數(shù)據(jù)查詢不可能被等同地使用,你要分清楚系統(tǒng)中的幾個(gè)關(guān)鍵查詢,這些查詢使用頻率高,響應(yīng)要快。試想一想,如果一個(gè)電子商務(wù)系統(tǒng)的產(chǎn)品查詢每次都要讓顧客等上十秒鐘,結(jié)果就可想而知。
用戶的使用習(xí)慣分析
除了對(duì)數(shù)據(jù)查詢本身需要進(jìn)行分析之外,我們還需要去分析一下用戶如何來(lái)使用或者看待這些數(shù)據(jù),用戶的使用習(xí)慣如何。有人可能覺(jué)得這作用不大,或者很難去分析,其實(shí)查詢的最終使用者是用戶,他們的一些習(xí)慣會(huì)很大程度上左右你的設(shè)計(jì)。
1、 用戶關(guān)心數(shù)據(jù)哪些方面的特性,不關(guān)心哪些方面的特性。
上面我們分析了數(shù)據(jù)本身的許多特性,那用戶對(duì)其中哪些特性最敏感呢?比如說(shuō)數(shù)據(jù)分析師對(duì)臟數(shù)據(jù)特別不能接受,那我們就必須在查詢框架設(shè)計(jì)時(shí)特別照顧到這一點(diǎn)。因?yàn)樵俸玫目蚣茉O(shè)計(jì)都不可能在每個(gè)方面都能達(dá)到最優(yōu)的效果,當(dāng)必須有所取舍的時(shí)候,我們就要明白哪些特性是客戶最關(guān)心的。
2、 用戶如何來(lái)使用數(shù)據(jù)。
現(xiàn)在一般查詢的客戶端都采用分頁(yè)的方式,一個(gè)查詢可能會(huì)存在十幾頁(yè)甚至幾十頁(yè)結(jié)果。對(duì)于某些查詢,用戶可能往往只關(guān)心第一頁(yè)或者前幾頁(yè)的結(jié)果,比如用戶需要查詢出最近完成的工單,而對(duì)于另外一些查詢,用戶可能對(duì)所有頁(yè)結(jié)果都很關(guān)注,比如用戶查詢出最近三天新增的招聘職位。這不同類型的查詢?cè)诓樵兛蚣茉O(shè)計(jì)的時(shí)候都需要有所考慮并給予不同的處理策略。
查詢框架的設(shè)計(jì)
對(duì)數(shù)據(jù)及用戶使用習(xí)慣進(jìn)行了仔細(xì)的分析,接下來(lái)就可以根據(jù)這些分析來(lái)設(shè)計(jì)你的查詢框架了。在J2EE架構(gòu)下,對(duì)于大數(shù)據(jù)量的查詢主要采取以下兩種方法:
基于緩存的方式:
從數(shù)據(jù)庫(kù)得到全部(部分)數(shù)據(jù),并將其在服務(wù)器端進(jìn)行緩存,接下來(lái)的客戶端請(qǐng)求,將直接從緩存中取得需要的數(shù)據(jù)。這其實(shí)就是Value List Handler模式的原理,它主要適用于數(shù)據(jù)量不是非常大,變化不是很頻繁(或者變化頻繁但是有規(guī)律)且不具有成長(zhǎng)性的情況,比如招聘網(wǎng)站或者電子商務(wù)網(wǎng)站的大部分查詢就非常適合采取這種方式。
采用這種方式,要特別注意第一次查詢問(wèn)題,避免響應(yīng)性能達(dá)不到要求,因?yàn)槊總€(gè)查詢第一次都需要連接數(shù)據(jù)庫(kù),從中獲取數(shù)據(jù)并緩存起來(lái),所以第一次查詢會(huì)比接下來(lái)的查詢都顯得更慢一些。
對(duì)于數(shù)據(jù)的緩存,有以下幾種實(shí)現(xiàn)方式:
? 直接緩存在服務(wù)器端
Value List Handler模式就采取這種方式,并且可以根據(jù)不同的情況采取不同的緩存策略,比如Transfer Object集合,CachedRowSet等,這取決于你的DAO實(shí)現(xiàn)策略。
? 用臨時(shí)表來(lái)保存查詢結(jié)果
WLDJ(www.sys-con.com/weblogic/)雜志2004年第7期上有一篇名為“Handling Large Database Result Sets”的文章,它詳細(xì)介紹了如何利用臨時(shí)表來(lái)改良Value List Handler模式以支持大型的J2EE應(yīng)用。
當(dāng)然除了以上這些方法以外,實(shí)現(xiàn)緩存也可以求助于操作系統(tǒng)的特定實(shí)現(xiàn),以前我在IBM DW發(fā)表過(guò)一篇探討MMF在Java中應(yīng)用的文章(見(jiàn)參考資料),可惜未有深入,有興趣的朋友可以參考一下。
在使用Value List Handler模式時(shí),要特別注意以下幾點(diǎn):
1、 該模式一般和DAO模式搭配使用。
2、 該模式有POJO,stateful session bean兩種實(shí)現(xiàn)策略。
3、 如果采取stateful session bean實(shí)現(xiàn)策略,則默認(rèn)該緩存的時(shí)間長(zhǎng)度為整個(gè)用戶會(huì)話。
前面我們也提到過(guò),如果數(shù)據(jù)不是絕對(duì)不變的,那緩存就面臨更新的問(wèn)題,一旦更新就可能存在著數(shù)據(jù)不一致,如果恰巧客戶也希望能夠看到變化的效果,這個(gè)時(shí)候就需要采取某種措施來(lái)保證這種一致性。常見(jiàn)的措施可以是設(shè)置一個(gè)標(biāo)志位,每次發(fā)生數(shù)據(jù)更新后都將其對(duì)應(yīng)的標(biāo)志位更新,查詢時(shí)如果發(fā)現(xiàn)標(biāo)志位更新了,就直接從數(shù)據(jù)庫(kù)獲取數(shù)據(jù),而不是從緩存中獲取數(shù)據(jù)。另外一種方式就是數(shù)據(jù)更新的同時(shí)主動(dòng)去清空session中的緩存,如果采用stateful session bean實(shí)現(xiàn)策略的話。
當(dāng)然,采取緩存方式的大數(shù)據(jù)量查詢一般來(lái)說(shuō)都不大可能遇到設(shè)置更新標(biāo)志位的問(wèn)題,因?yàn)檫@種應(yīng)用方式?jīng)Q定了數(shù)據(jù)不大可能變化,或者數(shù)據(jù)變化不要求立刻反應(yīng)給用戶。比如招聘網(wǎng)站新增加了一些職位信息,如果這些更新恰巧發(fā)生在某些用戶的會(huì)話期間,且沒(méi)有設(shè)置更新標(biāo)志位,那這些新增信息就不會(huì)反應(yīng)到用戶的查詢結(jié)果中,這種處理方式也是可以接受的。
基于查詢的方式:
不進(jìn)行數(shù)據(jù)緩存,客戶端的每次數(shù)據(jù)請(qǐng)求都需要進(jìn)行實(shí)際的數(shù)據(jù)庫(kù)查詢,這種方式適用于量大,具有成長(zhǎng)性,變化頻繁的數(shù)據(jù)。該方式的特點(diǎn)是每次查詢的時(shí)間都大致相等,不會(huì)存在基于緩存的方式的第一次查詢問(wèn)題,但后續(xù)的操作會(huì)比緩存方式的查詢慢一些。采取這種方式的查詢框架設(shè)計(jì)更具有可擴(kuò)展性以及對(duì)數(shù)據(jù)變化更好的應(yīng)變能力,在大部分的業(yè)務(wù)系統(tǒng)中都推薦使用該方式。
"數(shù)據(jù)分析師"使用這種方法,每次查詢應(yīng)該只從數(shù)據(jù)庫(kù)獲得客戶端所需的數(shù)據(jù),這樣就涉及到如何獲得部分?jǐn)?shù)據(jù)的問(wèn)題。一種是查詢出符合條件的所有記錄,然后遍歷該記錄集根據(jù)上次查詢結(jié)果來(lái)比較記錄中的某些字段獲取本次查詢需要的部分?jǐn)?shù)據(jù),由于要對(duì)記錄集進(jìn)行遍歷,效率不高,一般都不推薦使用,而往往采用另一種增加sql查詢語(yǔ)句條件的方式,這種方式有以下幾種實(shí)現(xiàn)策略:
? 專屬于數(shù)據(jù)庫(kù)的,比如Oracle的rownum
有些數(shù)據(jù)庫(kù)提供了標(biāo)識(shí)查詢結(jié)果集中行號(hào)的功能,利用該標(biāo)識(shí)就可以限定某個(gè)范圍的記錄,比如下面這個(gè)方法就是利用Oracle數(shù)據(jù)庫(kù)中的rownum功能來(lái)包裝sql查詢語(yǔ)句以獲得部分記錄集。
private String wrapSQL(String strSql) {
String strWrapSql = "";
strWrapSql = "select * from (select rownum mynum, xxx.* from (" + strSql + ") xxx ) where mynum between " + getFrom() + " and " + getTo();
return strWrapSql;
}
當(dāng)然這種實(shí)現(xiàn)策略的缺點(diǎn)很明顯,就是綁定于特定數(shù)據(jù)庫(kù)的實(shí)現(xiàn),有的數(shù)據(jù)庫(kù)可能并不提供這個(gè)功能,不能獨(dú)立于特定數(shù)據(jù)庫(kù)實(shí)現(xiàn)而提供一個(gè)通用的實(shí)現(xiàn)。
? 利用業(yè)務(wù)數(shù)據(jù)的規(guī)律性來(lái)獲得部分記錄集
對(duì)于實(shí)際系統(tǒng)中的每個(gè)查詢,都會(huì)被指定按照某種方式來(lái)進(jìn)行排序(不管是業(yè)務(wù)邏輯默認(rèn)的還是客戶端指定的),也就是說(shuō)查詢的sql中存在著order by子句。既然查詢結(jié)果集對(duì)于order by子句中的字段會(huì)呈現(xiàn)一定的規(guī)律性,那我們是不是可以利用這種有序性來(lái)獲得部分的記錄集呢?答案是肯定的。
參考資料中的Paging in J2EE一文對(duì)這種方式進(jìn)行了詳細(xì)的闡述,并且給出了一個(gè)實(shí)例,有興趣的朋友可以參考一下。但是,該篇文章針對(duì)的是那些排序方式是業(yè)務(wù)邏輯默認(rèn)的情況,即排序字段可以通過(guò)屬性文件定義下來(lái)的。但在實(shí)際情況中,還存在另外一種可能,排序方式是由客戶端指定時(shí),這個(gè)時(shí)候排序的字段和升降序都由客戶端指定,這個(gè)時(shí)候就需要在查詢框架中提供某種機(jī)制來(lái)將排序字段和where子句進(jìn)行映射。
獲得結(jié)果集的總數(shù)
當(dāng)采用獲取部分記錄集的方式時(shí),獲得的記錄集大小并不能真實(shí)地反映出查詢結(jié)果集的大小(因?yàn)橹皇瞧渲械囊徊糠郑?,所以又涉及到?duì)于記錄總數(shù)的處理:
? 如果不需要,或者沒(méi)有意義,不提供,因?yàn)閏ount(*)操作耗資源
? 在sql語(yǔ)句中獲得
在有些情況下,你可以在sql語(yǔ)句中直接加上count(*)來(lái)獲得記錄集總數(shù),比如你用Statement接口的setMaxRows(int max)方法來(lái)控制返回的記錄數(shù)時(shí)。
? 單獨(dú)進(jìn)行一次查詢
為count(*)語(yǔ)句單獨(dú)進(jìn)行一次數(shù)據(jù)庫(kù)查詢,只要將構(gòu)造好的sql語(yǔ)句中的select字段替換為count(*)即可。當(dāng)然count(*)的查詢sql應(yīng)該盡量簡(jiǎn)練,不要加上order by子句之類的。
其實(shí),以上提到的這兩種方式在實(shí)際的查詢框架設(shè)計(jì)時(shí)不可能完全分開(kāi)來(lái),只不過(guò)以哪種方式為主而已,它們往往是混合在一起使用以達(dá)到最優(yōu)的查詢效果。
查詢框架設(shè)計(jì)時(shí)的注意點(diǎn)
當(dāng)然,在優(yōu)化大數(shù)據(jù)量查詢的過(guò)程中,數(shù)據(jù)庫(kù)本身的優(yōu)化必不可少,比如建立索引等措施。但是,數(shù)據(jù)庫(kù)的優(yōu)化要循序漸進(jìn),要和程序代碼的優(yōu)化相一致。在進(jìn)行數(shù)據(jù)庫(kù)優(yōu)化之前,要做的是sql語(yǔ)句的優(yōu)化,這可以通過(guò)觀察它的執(zhí)行計(jì)劃來(lái)進(jìn)行考量。這些工作最好都由DBA來(lái)參與共同完成。
對(duì)于性能的個(gè)人態(tài)度:要提早考慮,確定一個(gè)方案前要進(jìn)行測(cè)試,并預(yù)見(jiàn)未來(lái)的變化。
對(duì)于“大數(shù)據(jù)量”這幾個(gè)詞不要只停留于口頭的重視,要想有切身的體會(huì),最好在框架設(shè)計(jì)好后,進(jìn)行大數(shù)據(jù)量的模擬壓力測(cè)試,實(shí)際地檢驗(yàn)?zāi)愕脑O(shè)計(jì)。不要總是回答:我想,應(yīng)該沒(méi)有問(wèn)題,應(yīng)該支持幾百萬(wàn)的數(shù)據(jù),最好拿出最有說(shuō)服力的數(shù)據(jù)。
以后關(guān)注的方向:
1、 EJB 3.0規(guī)范中EJB QL語(yǔ)法得到加強(qiáng),比如可以支持原生SQL語(yǔ)句,使得利用實(shí)體Bean來(lái)實(shí)現(xiàn)部分類型查詢成為可能。
2、 JDBC規(guī)范是否能夠在某些方面對(duì)這種大數(shù)據(jù)量查詢提供某些功能接口,比如獲得部分記錄集,以及統(tǒng)一的緩存接口。
總結(jié)
本文給出了查詢框架設(shè)計(jì)時(shí)的一些思路和方法,最重要的一點(diǎn)就是在設(shè)計(jì)之前要充分研究你的目標(biāo)系統(tǒng),了解最終的數(shù)據(jù)查詢特點(diǎn)。由于本文只是個(gè)人的一些觀點(diǎn)和經(jīng)驗(yàn)總結(jié),偏頗之處在所難免,希望有興趣的朋友一起來(lái)討論這個(gè)問(wèn)題。數(shù)據(jù)分析師培訓(xùn)
CDA數(shù)據(jù)分析師考試相關(guān)入口一覽(建議收藏):
? 想報(bào)名CDA認(rèn)證考試,點(diǎn)擊>>>
“CDA報(bào)名”
了解CDA考試詳情;
? 想學(xué)習(xí)CDA考試教材,點(diǎn)擊>>> “CDA教材” 了解CDA考試詳情;
? 想加入CDA考試題庫(kù),點(diǎn)擊>>> “CDA題庫(kù)” 了解CDA考試詳情;
? 想了解CDA考試含金量,點(diǎn)擊>>> “CDA含金量” 了解CDA考試詳情;