soufan

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            22 隨筆 :: 0 文章 :: 0 評論 :: 0 Trackbacks

          from:http://www.javaeye.com/topic/9706

          數(shù)據(jù)庫對象的緩存策略

          前言
          本文探討Jive(曾經(jīng)開源的Java論壇)和Hibernate(Java開源持久層)的數(shù)據(jù)庫對象的緩存策略,并闡述作者本人的Lightor(Java開源持久層)采用的數(shù)據(jù)庫對象緩存策略。
          本文的探討基于以前開源的Jive代碼,Hibernate2.1.7源碼,和作者本人的Lightor代碼。
          本文用ID (Identifier的縮寫)來代表數(shù)據(jù)記錄的關(guān)鍵字。
          數(shù)據(jù)對象查詢一般分為兩種:條件查詢,返回一個滿足條件的數(shù)據(jù)對象列表; ID查詢,返回ID對應(yīng)的數(shù)據(jù)對象。
          本文主要探討“條件查詢”和“ID查詢”這兩種情況的緩存策略。
          本文只探討一個JVM內(nèi)的數(shù)據(jù)緩存策略,不涉及分布式緩存;本文只探討對應(yīng)單表的數(shù)據(jù)對象的緩存,不涉及關(guān)聯(lián)表對象的情況。

          一、Jive的緩存策略
          1.Jive的緩存策略的過程描述:
          (1)條件查詢的時候,Jive用 select id from table_name where …. (只選擇ID字段)這樣的SQL語句查詢數(shù)據(jù)庫,來獲得一個ID列表。
          (2) Jive根據(jù)ID列表中的每個ID,首先查看緩存中是否存在對應(yīng)ID的數(shù)據(jù)對象:如果存在,那么直接取出,加入到 結(jié)果列表中;如果不存在,那么通過一條select * from table_name where id = {ID value} 這樣的SQL查詢數(shù)據(jù)庫,取出對應(yīng)的數(shù)據(jù)對象,放入到結(jié)果列表,并把這個數(shù)據(jù)對象按照ID放入到緩存中。
          (3) ID查詢的時候,Jive執(zhí)行類似第(2)步的過程,先從緩存中查找該ID,查不到,再查詢數(shù)據(jù)庫,然后把結(jié)果放入到緩存。
          (4) 刪除、更新、增加數(shù)據(jù)的時候,同時更新緩存。
          2.Jive緩存策略的優(yōu)點(diǎn):
          (1) ID查詢的時候,如果該ID已經(jīng)存在于緩存中,那么可以直接取出。節(jié)省了一條數(shù)據(jù)庫查詢。
          (2) 當(dāng)多次條件查詢的結(jié)果集相交的情況下,交集里面的數(shù)據(jù)對象不用重復(fù)從數(shù)據(jù)庫整個獲取,直接從緩存中獲取即可。
          比如,第一次查詢的ID列表為{1, 2},然后根據(jù)ID列表的ID從數(shù)據(jù)庫中一個一個取出數(shù)據(jù)對象,結(jié)果集為{a(id = 1), b(id = 2)}。
          下一次查詢的ID列表為{2, 3},由于ID = 2的數(shù)據(jù)對象已經(jīng)存在于緩存中,那么只要從數(shù)據(jù)庫中取出ID = 3的數(shù)據(jù)對象即可。
          3.Jive緩存策略的缺點(diǎn):
          (1) 在根據(jù)條件查找數(shù)據(jù)對象列表的過程中,DAO的第(1)步用來獲得ID列表的那一次數(shù)據(jù)庫查詢,是必不可少的。
          (2) 如果第(1)步返回的ID列表中有n個ID,在最壞的命中率(緩存中一個對應(yīng)ID都沒有)情況下,Jive還要再查詢n次數(shù)據(jù)庫。最壞情況下,共需要n + 1數(shù)據(jù)庫查詢。

          二、Hibernate的二級緩存策略
          Hibernate用Session類包裝了數(shù)據(jù)庫連接從打開到關(guān)閉的過程。
          Session內(nèi)部維護(hù)一個數(shù)據(jù)對象集合,包括了本Session內(nèi)選取的、操作的數(shù)據(jù)對象。這稱為Session內(nèi)部緩存,是Hibernate的第一級最快緩存,屬于Hibernate的既定行為,不需要進(jìn)行配置(也沒有辦法配置 :-)。
          Session的生命期很短,存在于Session內(nèi)部的第一級最快緩存的生命期當(dāng)然也很短,命中率自然也很低。當(dāng)然,這個Session內(nèi)部緩存的主要作用是保持Session內(nèi)部數(shù)據(jù)狀態(tài)同步。
          如果需要跨Session的命中率較高的全局緩存,那么必須對Hibernate進(jìn)行二級緩存配置。一般來說,同樣數(shù)據(jù)類型(Class)的數(shù)據(jù)對象,共用一個二級緩存(或其中的同一塊)。
          1.Hibernate二級緩存策略的過程描述:
          (1)條件查詢的時候,總是發(fā)出一條select * from table_name where …. (選擇所有字段)這樣的SQL語句查詢數(shù)據(jù)庫,一次獲得所有的數(shù)據(jù)對象。
          (2) 把獲得的所有數(shù)據(jù)對象根據(jù)ID放入到第二級緩存中。
          (3) 當(dāng)Hibernate根據(jù)ID訪問數(shù)據(jù)對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那么從二級緩存中查;查不到,再查詢數(shù)據(jù)庫,把結(jié)果按照ID放入到緩存。
          (4) 刪除、更新、增加數(shù)據(jù)的時候,同時更新緩存。

          2.Hibernate二級緩存策略的優(yōu)點(diǎn):
          (1) 具有Jive緩存策略同樣的第(1)條優(yōu)點(diǎn):ID查詢的時候,如果該ID已經(jīng)存在于緩存中,那么可以直接取出。節(jié)省了一條數(shù)據(jù)庫查詢。
          (2) 不具有Jive緩存策略的第(2)條缺點(diǎn),即hibernate不會有最壞情況下的 n + 1次數(shù)據(jù)庫查詢。
          3.Hibernate二級緩存策略的缺點(diǎn):
          (1) 同Jive緩存策略的第(1)條缺點(diǎn)一樣,條件查詢的時候,第(1)步的數(shù)據(jù)庫查詢語句是不可少的。而且Hibernate選擇所有的字段,比只選擇ID字段花費(fèi)的時間和空間都多。
          (2) 不具備Jive緩存策略的第(2)條優(yōu)點(diǎn)。條件查詢的時候,必須把數(shù)據(jù)庫對象從數(shù)據(jù)庫中整個取出,即使該數(shù)據(jù)庫的ID已經(jīng)存在于緩存中。

          三、Hibernate的Query緩存策略
          可以看到,Jive緩存和Hibernate的二級緩存策略,都只是針對于ID查詢的緩存策略,對于條件查詢則毫無作用。(盡管Jive緩存的第(2)個優(yōu)點(diǎn),能夠避免重復(fù)從數(shù)據(jù)庫獲取同一個ID對應(yīng)的數(shù)據(jù)對象,但select id from …這條數(shù)據(jù)庫查詢是每次條件查詢都必不可少的)。
          為此,Hibernate提供了針對條件查詢的Query緩存。
          1.Hibernate的Query緩存策略的過程描述:
          (1) 條件查詢的請求一般都包括如下信息:SQL, SQL需要的參數(shù),記錄范圍(起始位置rowStart,最大記錄個數(shù)maxRows),等。
          (2) Hibernate首先根據(jù)這些信息組成一個Query Key,根據(jù)這個Query Key到Query緩存中查找對應(yīng)的結(jié)果列表。如果存在,那么返回這個結(jié)果列表;如果不存在,查詢數(shù)據(jù)庫,獲取結(jié)果列表,把整個結(jié)果列表根據(jù)Query Key放入到Query緩存中。
          (3) Query Key中的SQL涉及到一些表名,如果這些表的任何數(shù)據(jù)發(fā)生修改、刪除、增加等操作,這些相關(guān)的Query Key都要從緩存中清空。
          2.Hibernate的Query緩存策略的優(yōu)點(diǎn)
          (1) 條件查詢的時候,如果Query Key已經(jīng)存在于緩存,那么不需要再查詢數(shù)據(jù)庫。命中的情況下,一次數(shù)據(jù)庫查詢也不需要。
          3.Hibernate的Query緩存策略的缺點(diǎn)
          (1) 條件查詢涉及到的表中,如果有任何一條記錄增加、刪除、或改變,那么緩存中所有和該表相關(guān)的Query Key都會失效。
          比如,有這樣幾組Query Key,它們的SQL里面都包括table1。
          SQL = select * from table1 where c1 = ? …., parameter = 1, rowStart = 11, maxRows = 20.
          SQL = select * from table1 where c1 = ? …., parameter = 1, rowStart = 21, maxRows = 20.
          SQL = select * from table1 where c1 = ? ….., parameter = 2, rowStart = 11, maxRows = 20.
          SQL = select * from table1 where c1 = ? ….., parameter = 2, rowStart = 11, maxRows = 20.
          SQL = select * from table1 where c2 = ? …., parameter = ‘a(chǎn)bc’, rowStart = 11, maxRows = 20.

          當(dāng)table1的任何數(shù)據(jù)對象(任何字段)改變、增加、刪除的時候,這些Query Key對應(yīng)的結(jié)果集都不能保證沒有發(fā)生變化。
          很難做到根據(jù)數(shù)據(jù)對象的改動精確判斷哪些Query Key對應(yīng)的結(jié)果集受到影響。最簡單的實(shí)現(xiàn)方法,就是清空所有SQL包含table1的Query Key。

          (2) Query緩存中,Query Key對應(yīng)的是數(shù)據(jù)對象列表,假如不同的Query Key對應(yīng)的數(shù)據(jù)對象列表有交集,那么,交集部分的數(shù)據(jù)對象就是重復(fù)存儲的。
          比如,Query Key 1對應(yīng)的數(shù)據(jù)對象列表為{a(id = 1), b(id = 2)},Query Key 2對應(yīng)的數(shù)據(jù)對象列表為{a(id = 1), c(id = 3)},這個a就在兩個List同時存在了兩份。

          4.二級緩存和Query緩存同步的困惑
          假如,Query緩存中,一個Query Key對應(yīng)的結(jié)果列表為{a (id = 1) , b (id = 2), c (id = 3)}; 二級緩存里面有也id = 1對應(yīng)的數(shù)據(jù)對象a。
          這兩個數(shù)據(jù)對象a之間是什么關(guān)系?能夠保持狀態(tài)同步嗎?
          我閱讀Hibernate的相關(guān)源碼,沒有發(fā)現(xiàn)兩個緩存之間的這種同步關(guān)系。
          或者兩者之間毫無關(guān)系。就像我上面所說的,只要表數(shù)據(jù)發(fā)生變化,相關(guān)的Query Key都要被清空。所以不用考慮同步問題?

          四、Lightor的緩存策略
          Lightor是我做的Java開源持久層框架。Lightor的意思是,Lightweight O/R。Hibernate,JDO,EJB CMP這些持久層框架,都是Layer。Lightor算不上Layer,而只是一個Helper。這里的O/R意思不是Object/Relational,而是Object/ResultSet的意思。:-)
          Lightor的緩存策略,主要參照Hibernate的緩存思路,Lightor的緩存也分為 Query緩存和ID緩存。但其中有一點(diǎn)不同,兩者之間并不是毫無聯(lián)系的,而是相互關(guān)聯(lián)的。
          1.Lightor的緩存策略的過程描述:
          (1) 條件查詢的請求一般都包括如下信息:SQL, 對應(yīng)SQL的參數(shù),起始記錄位置(rowStart),最大記錄個數(shù)(maxRows),等。
          (2) Lightor首先根據(jù)這些信息組成一個Query Key,根據(jù)這個Query Key到Query緩存中查找對應(yīng)的結(jié)果ID列表。注意,這里獲取的是ID列表。
          如果結(jié)果ID列表存在于Query緩存,那么根據(jù)這個ID列表的每個ID,到ID緩存中取對應(yīng)的數(shù)據(jù)對象。如果所有ID對應(yīng)的數(shù)據(jù)對象都找到,那個返回這個數(shù)據(jù)對象結(jié)果列表。注意,這里獲取的是整個數(shù)據(jù)對象(所有字段)的列表。
          如果結(jié)果ID列表不存在于Query緩存,或者結(jié)果ID列表中的某一個ID不存在于ID緩存,那么,就查詢數(shù)據(jù)庫,獲取結(jié)果列表。然后,把獲取的每個數(shù)據(jù)對象按照ID放入到ID緩存;并組裝成一個ID列表,按照Query Key存放到Query緩存中。注意,這里是把ID列表,而不是整個對象列表,放入到Query緩存中。
          (3) ID查詢的時候,Lightor先從ID緩存中查找該ID,如果不存在,那么查詢數(shù)據(jù)庫,把結(jié)果放入ID緩存。
          (4) Query Key中的SQL涉及到一些表名,如果這些表的任何數(shù)據(jù)發(fā)生修改、刪除、增加等操作,這些相關(guān)的Query Key都要從緩存中清空。
          2.Lightor的緩存策略的優(yōu)點(diǎn)
          (1) Lightor的ID緩存具有Jive緩存,和Hibernate二級ID緩存的優(yōu)點(diǎn)。ID查詢的時候,如果該ID已經(jīng)存在于緩存中,那么可以直接取出。節(jié)省了一條數(shù)據(jù)庫查詢。
          (2) Lightor的Query緩存具有Hibernate的Query緩存的優(yōu)點(diǎn)。條件查詢的時候,如果Query Key已經(jīng)存在于緩存,那么不需要再查詢數(shù)據(jù)庫。命中的情況下,一次數(shù)據(jù)庫查詢也不需要。
          (3) Lightor的Query緩存中,Query Key對應(yīng)的是ID列表,而不是數(shù)據(jù)對象列表,真正的數(shù)據(jù)對象只存在于ID緩存中。所以,不同的Query Key對應(yīng)的ID列表如果有交集,ID對應(yīng)的數(shù)據(jù)對象也不會在ID緩存中重復(fù)存儲。
          (4) Lightor的緩存也沒有Jive緩存的最壞情況n + 1次數(shù)據(jù)庫查詢?nèi)秉c(diǎn)。
          3.Lightor的緩存策略的缺點(diǎn)
          (1) Lightor的Query緩存具有Hibernate的Query緩存的缺點(diǎn)。條件查詢涉及到的表中,如果有任何一條記錄增加、刪除、或改變,那么緩存中所有和該表相關(guān)的Query Key都會失效。
          (2) Lightor的ID緩存也具有hibernate的二級ID緩存具有的缺點(diǎn)。條件查詢的時候,即使ID已經(jīng)存在于緩存中,也需要重新把數(shù)據(jù)對象整個從數(shù)據(jù)庫取出,放入到緩存中。

          五、Query Key的效率
          Query緩存的Query Key的空間和時間開銷比較大。
          Query Key里面存放的東西不少,SQL, 參數(shù),范圍(起始,個數(shù))。
          這里面最大的東西就是SQL。又占地方,又花時間(hashCode, equals)。
          Query Key最關(guān)鍵的兩個方法是hashCode和equals,重點(diǎn)是SQL的hashCode和equals。

          Lightor的做法是,由于Lightor直接使用SQL,不用HQL、OQL之類,所以推薦盡量使用static final String的SQL,能夠節(jié)省空間和時間,以至于Query Key的效率能夠相當(dāng)于ID Key的效率。
          至于Hibernate的QueryKey,有興趣的讀者可以去下載閱讀Hibernate的各個版本的源代碼,跟蹤一下QueryKey的實(shí)現(xiàn)優(yōu)化過程。

          六、總結(jié)
          這里列一個表,綜合表示Jive, Hibernate, Lightor的緩存策略的特征。
          N + 1問題 重復(fù)ID緩存問題 Query緩存支持
          Jive緩存 有 無 不支持
          Hibernate緩存 無 有 支持
          Lightor緩存 無 有 支持

          注:
          “重復(fù)ID緩存問題”的含義是,每次條件查詢,不是只取ID列表,而是取出完整對象(所有字段)的列表。這樣,同一個ID對應(yīng)的數(shù)據(jù)對象,即使在緩存中已經(jīng)存在,也可能被重新放入緩存。參見相關(guān)緩存的缺點(diǎn)描述。
          “重復(fù)ID緩存問題”的負(fù)面效應(yīng)到底有多大,就看你的select id from …(只選擇ID)比你的 select * from … (選擇所有字段)快多少。主要影響因素是,字段的個數(shù),字段值的長度,與數(shù)據(jù)庫服務(wù)器之間網(wǎng)絡(luò)傳輸速度。
          不管怎么說,即使選擇所有字段,也只是一次數(shù)據(jù)庫查詢。而N + 1問題帶來的可能最壞的負(fù)面效應(yīng)(N + 1次數(shù)據(jù)查詢)卻是非常大的。
          選擇緩存策略的時候,應(yīng)根據(jù)這些情況發(fā)生的概率和正負(fù)面效應(yīng)進(jìn)行取舍。

          ----- added later

          看到Robbin在04年6月的一篇相關(guān)文章。

          Hibernate Iterator JCS分析
          http://www.hibernate.org.cn/71.html

          Hibernate Iterator JCS分析 寫道

          而Hibernate List方式是JDBC的簡單封裝,一次sql就把所有的數(shù)據(jù)都取出來了,它不會像Iterator那樣先取主鍵,然后再取數(shù)據(jù),因此List無法利用JCS。不過List也可以把從數(shù)據(jù)庫中取出的數(shù)據(jù)填充到JCS里面去。

          最佳的方式:第一次訪問使用List,快速填充JCS,以后訪問采用Iterator,充分利用JCS。

          posted on 2006-10-11 08:35 soufan 閱讀(208) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 大丰市| 金湖县| 县级市| 江川县| 内江市| 鹤岗市| 桐庐县| 雅安市| 昭觉县| 福贡县| 亳州市| 柳河县| 准格尔旗| 满城县| 高尔夫| 革吉县| 江口县| 邹平县| 宜丰县| 介休市| 瓮安县| 丰城市| 曲周县| 宣恩县| 龙江县| 广安市| 望江县| 青川县| 鄂尔多斯市| 宁蒗| 华容县| 祁连县| 阳泉市| 奉贤区| 文登市| 达州市| 甘孜县| 石屏县| 石渠县| 谢通门县| 永登县|