Hibernate- hibernate二級緩衝

來源:互聯網
上載者:User

標籤:idle   --   一個   字串   factory   簡單   很多   文章   tco   

原文地址:http://www.iteye.com/topic/18904

 

很多人對二級緩衝都不太瞭解,或者是有錯誤的認識,我一直想寫一篇文章介紹一下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。假設是讀寫緩衝,需要設定:
<cache usage="read-write"/>
如果你使用的二級緩衝實現是ehcache的話,需要配置ehcache.xml
<cache name="com.xxx.pojo.Foo" maxElementsInMemory="500" eternal="false" timeToLiveSeconds="7200" timeToIdleSeconds="3600" overflowToDisk="true" />
其中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的包名了
<cache name="net.sf.hibernate.cache.StandardQueryCache"
   maxElementsInMemory="50" eternal="false" timeToIdleSeconds="3600"
   timeToLiveSeconds="7200" overflowToDisk="true"/>
<cache name="net.sf.hibernate.cache.UpdateTimestampsCache"
   maxElementsInMemory="5000" eternal="true" overflowToDisk="true"/>
然後
query.setCacheable(true);//啟用查詢快取
query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion,可選
第二行指定要使用的cacheRegion是myCacheRegion,即你可以給每個查詢快取做一個單獨的配置,使用setCacheRegion來做這個指定,需要在ehcache.xml裡面配置它:
<cache name="myCacheRegion" maxElementsInMemory="10" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200" overflowToDisk="true" />
如果省略第二行,不設定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裡面設定
<cache usage="read-write"/>
假如class是Cat,collection叫children,那麼ehcache裡面配置
<cache name="com.xxx.pojo.Cat.children"
   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的諸多限制,那麼還是自己在應用程式的層面上做緩衝吧。
在越高的層面上做緩衝,效果就會越好。就好像儘管磁碟有緩衝,資料庫還是要實現自己的緩衝,儘管資料庫有緩衝,咱們的應用程式還是要做緩衝。因為底層的緩衝它並不知道高層要用這些資料幹什麼,只能做的比較通用,而高層可以有針對性的實現緩衝,所以在更高的層級上做緩衝,效果也要好些吧。


終於寫完了,好累……

Hibernate- hibernate二級緩衝

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.