posts - 73,  comments - 55,  trackbacks - 0
          ?很多人對二級緩存都不太了解,或者是有錯誤的認識,我一直想寫一篇文章介紹一下hibernate的二級緩存的,今天終于忍不住了。?
          我的經驗主要來自hibernate2.1版本,基本原理和3.0、3.1是一樣的,請原諒我的頑固不化。?

          hibernate的session提供了一級緩存,每個session,對同一個id進行兩次load,不會發送兩條sql給數據庫,但是session關閉的時候,一級緩存就失效了。?

          二級緩存是SessionFactory級別的全局緩存,它底下可以使用不同的緩存類庫,比如ehcache、oscache等,需要設置hibernate.cache.provider_class,我們這里用ehcache,在2.1中就是?
          hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider?
          如果使用查詢緩存,加上?
          hibernate.cache.use_query_cache=true?


          緩存可以簡單的看成一個Map,通過key在緩存里面找value。?

          Class的緩存?
          對于一條記錄,也就是一個PO來說,是根據ID來找的,緩存的key就是ID,value是POJO。無論list,load還是iterate,只要讀出一個對象,都會填充緩存。但是list不會使用緩存,而iterate會先取數據庫select?id出來,然后一個id一個id的load,如果在緩存里面有,就從緩存取,沒有的話就去數據庫load。假設是讀寫緩存,需要設置:?
          ?
          如果你使用的二級緩存實現是ehcache的話,需要配置ehcache.xml?
          ?
          其中eternal表示緩存是不是永遠不超時,timeToLiveSeconds是緩存中每個元素(這里也就是一個POJO)的超時時間,如果 eternal="false",超過指定的時間,這個元素就被移走了。timeToIdleSeconds是發呆時間,是可選的。當往緩存里面put的元素超過500個時,如果overflowToDisk="true",就會把緩存中的部分數據保存在硬盤上的臨時文件里面。?
          每個需要緩存的class都要這樣配置。如果你沒有配置,hibernate會在啟動的時候警告你,然后使用defaultCache的配置,這樣多個class會共享一個配置。?
          當某個ID通過hibernate修改時,hibernate會知道,于是移除緩存。?
          這樣大家可能會想,同樣的查詢條件,第一次先list,第二次再iterate,就可以使用到緩存了。實際上這是很難的,因為你無法判斷什么時候是第一次,而且每次查詢的條件通常是不一樣的,假如數據庫里面有100條記錄,id從1到100,第一次list的時候出了前50個id,第二次iterate的時候卻查詢到30至70號id,那么30-50是從緩存里面取的,51到70是從數據庫取的,共發送1+20條sql。所以我一直認為iterate沒有什么用,總是會有1+N的問題。?
          (題外話:有說法說大型查詢用list會把整個結果集裝入內存,很慢,而iterate只select?id比較好,但是大型查詢總是要分頁查的,誰也不會真的把整個結果集裝進來,假如一頁20條的話,iterate共需要執行21條語句,list雖然選擇若干字段,比iterate第一條select?id語句慢一些,但只有一條語句,不裝入整個結果集hibernate還會根據數據庫方言做優化,比如使用 mysql的limit,整體看來應該還是list快。)?
          如果想要對list或者iterate查詢的結果緩存,就要用到查詢緩存了?

          查詢緩存?
          首先需要配置hibernate.cache.use_query_cache=true?
          如果用ehcache,配置ehcache.xml,注意hibernate3.0以后不是net.sf的包名了?

          maxElementsInMemory="50"?eternal="false"?timeToIdleSeconds="3600"?
          timeToLiveSeconds="7200"?overflowToDisk="true"/>?

          maxElementsInMemory="5000"?eternal="true"?overflowToDisk="true"/>?
          然后?
          query.setCacheable(true);//激活查詢緩存?
          query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion,可選?
          第二行指定要使用的cacheRegion是myCacheRegion,即你可以給每個查詢緩存做一個單獨的配置,使用setCacheRegion來做這個指定,需要在ehcache.xml里面配置它:?
          ?
          如果省略第二行,不設置cacheRegion的話,那么會使用上面提到的標準查詢緩存的配置,也就是net.sf.hibernate.cache.StandardQueryCache?

          對于查詢緩存來說,緩存的key是根據hql生成的sql,再加上參數,分頁等信息(可以通過日志輸出看到,不過它的輸出不是很可讀,最好改一下它的代碼)。?
          比如hql:?
          from?Cat?c?where?c.name?like???
          生成大致如下的sql:?
          select?*?from?cat?c?where?c.name?like???
          參數是"tiger%",那么查詢緩存的key*大約*是這樣的字符串(我是憑記憶寫的,并不精確,不過看了也該明白了):?
          select?*?from?cat?c?where?c.name?like???,?parameter:tiger%?
          這樣,保證了同樣的查詢、同樣的參數等條件下具有一樣的key。?
          現在說說緩存的value,如果是list方式的話,value在這里并不是整個結果集,而是查詢出來的這一串ID。也就是說,不管是list方法還是 iterate方法,第一次查詢的時候,它們的查詢方式很它們平時的方式是一樣的,list執行一條sql,iterate執行1+N條,多出來的行為是它們填充了緩存。但是到同樣條件第二次查詢的時候,就都和iterate的行為一樣了,根據緩存的key去緩存里面查到了value,value是一串 id,然后在到class的緩存里面去一個一個的load出來。這樣做是為了節約內存。?
          可以看出來,查詢緩存需要打開相關類的class緩存。list和iterate方法第一次執行的時候,都是既填充查詢緩存又填充class緩存的。?
          這里還有一個很容易被忽視的重要問題,即打開查詢緩存以后,即使是list方法也可能遇到1+N的問題!相同條件第一次list的時候,因為查詢緩存中找不到,不管class緩存是否存在數據,總是發送一條sql語句到數據庫獲取全部數據,然后填充查詢緩存和class緩存。但是第二次執行的時候,問題就來了,如果你的class緩存的超時時間比較短,現在class緩存都超時了,但是查詢緩存還在,那么list方法在獲取id串以后,將會一個一個去數據庫 load!因此,class緩存的超時時間一定不能短于查詢緩存設置的超時時間!如果還設置了發呆時間的話,保證class緩存的發呆時間也大于查詢的緩存的生存時間。這里還有其他情況,比如class緩存被程序強制evict了,這種情況就請自己注意了。?

          另外,如果hql查詢包含select字句,那么查詢緩存里面的value就是整個結果集了。?

          當hibernate更新數據庫的時候,它怎么知道更新哪些查詢緩存呢??
          hibernate在一個地方維護每個表的最后更新時間,其實也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的緩存配置里面。?
          當通過hibernate更新的時候,hibernate會知道這次更新影響了哪些表。然后它更新這些表的最后更新時間。每個緩存都有一個生成時間和這個緩存所查詢的表,當hibernate查詢一個緩存是否存在的時候,如果緩存存在,它還要取出緩存的生成時間和這個緩存所查詢的表,然后去查找這些表的最后更新時間,如果有一個表在生成時間后更新過了,那么這個緩存是無效的。?
          可以看出,只要更新過一個表,那么凡是涉及到這個表的查詢緩存就失效了,因此查詢緩存的命中率可能會比較低。?

          Collection緩存?
          需要在hbm的collection里面設置?
          ?
          假如class是Cat,collection叫children,那么ehcache里面配置?

          maxElementsInMemory="20"?eternal="false"?timeToIdleSeconds="3600"?timeToLiveSeconds="7200"?
          overflowToDisk="true"?/>?
          Collection的緩存和前面查詢緩存的list一樣,也是只保持一串id,但它不會因為這個表更新過就失效,一個collection緩存僅在這個collection里面的元素有增刪時才失效。?
          這樣有一個問題,如果你的collection是根據某個字段排序的,當其中一個元素更新了該字段時,導致順序改變時,collection緩存里面的順序沒有做更新。?

          緩存策略?
          只讀緩存(read-only):沒有什么好說的?
          讀/寫緩存(read-write):程序可能要的更新數據?
          不嚴格的讀/寫緩存(nonstrict-read-write):需要更新數據,但是兩個事務更新同一條記錄的可能性很小,性能比讀寫緩存好?
          事務緩存(transactional):緩存支持事務,發生異常的時候,緩存也能夠回滾,只支持jta環境,這個我沒有怎么研究過?

          讀寫緩存和不嚴格讀寫緩存在實現上的區別在于,讀寫緩存更新緩存的時候會把緩存里面的數據換成一個鎖,其他事務如果去取相應的緩存數據,發現被鎖住了,然后就直接取數據庫查詢。?
          在hibernate2.1的ehcache實現中,如果鎖住部分緩存的事務發生了異常,那么緩存會一直被鎖住,直到60秒后超時。?
          不嚴格讀寫緩存不鎖定緩存中的數據。?


          使用二級緩存的前置條件?
          你的hibernate程序對數據庫有獨占的寫訪問權,其他的進程更新了數據庫,hibernate是不可能知道的。你操作數據庫必需直接通過 hibernate,如果你調用存儲過程,或者自己使用jdbc更新數據庫,hibernate也是不知道的。hibernate3.0的大批量更新和刪除是不更新二級緩存的,但是據說3.1已經解決了這個問題。?
          這個限制相當的棘手,有時候hibernate做批量更新、刪除很慢,但是你卻不能自己寫jdbc來優化,很郁悶吧。?
          SessionFactory也提供了移除緩存的方法,你一定要自己寫一些JDBC的話,可以調用這些方法移除緩存,這些方法是:?
          void?evict(Class?persistentClass)?
          Evict?all?entries?from?the?second-level?cache.?
          void?evict(Class?persistentClass,?Serializable?id)?
          Evict?an?entry?from?the?second-level?cache.?
          void?evictCollection(String?roleName)?
          Evict?all?entries?from?the?second-level?cache.?
          void?evictCollection(String?roleName,?Serializable?id)?
          Evict?an?entry?from?the?second-level?cache.?
          void?evictQueries()?
          Evict?any?query?result?sets?cached?in?the?default?query?cache?region.?
          void?evictQueries(String?cacheRegion)?
          Evict?any?query?result?sets?cached?in?the?named?query?cache?region.?
          不過我不建議這樣做,因為這樣很難維護。比如你現在用JDBC批量更新了某個表,有3個查詢緩存會用到這個表,用evictQueries (String?cacheRegion)移除了3個查詢緩存,然后用evict(Class?persistentClass)移除了class緩存,看上去好像完整了。不過哪天你添加了一個相關查詢緩存,可能會忘記更新這里的移除代碼。如果你的jdbc代碼到處都是,在你添加一個查詢緩存的時候,還知道其他什么地方也要做相應的改動嗎??

          ----------------------------------------------------?

          總結:?
          不要想當然的以為緩存一定能提高性能,僅僅在你能夠駕馭它并且條件合適的情況下才是這樣的。hibernate的二級緩存限制還是比較多的,不方便用jdbc可能會大大的降低更新性能。在不了解原理的情況下亂用,可能會有1+N的問題。不當的使用還可能導致讀出臟數據。?
          如果受不了hibernate的諸多限制,那么還是自己在應用程序的層面上做緩存吧。?
          在越高的層面上做緩存,效果就會越好。就好像盡管磁盤有緩存,數據庫還是要實現自己的緩存,盡管數據庫有緩存,咱們的應用程序還是要做緩存。因為底層的緩存它并不知道高層要用這些數據干什么,只能做的比較通用,而高層可以有針對性的實現緩存,所以在更高的級別上做緩存,效果也要好些吧。?
          posted on 2006-09-27 13:39 保爾任 閱讀(467) 評論(0)  編輯  收藏

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


          網站導航:
           

          <2025年5月>
          27282930123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          常用鏈接

          留言簿(4)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 沧州市| 饶平县| 二手房| 西乡县| 莆田市| 灵武市| 太谷县| 瑞金市| 汤阴县| 错那县| 昭平县| 乳山市| 凤城市| 康保县| 云安县| 乌兰察布市| 乐昌市| 南丰县| 娱乐| 正宁县| 金昌市| 日照市| 涞源县| 祁阳县| 旬阳县| 木里| 洪泽县| 泾阳县| 河北区| 丹阳市| 珠海市| 昌吉市| 台南县| 临汾市| 赣榆县| 南皮县| 大同县| 刚察县| 拉萨市| 乐山市| 文登市|