Hibernate性能優化

1、針對Mysql數據庫而言主要是物理硬件的優化磁盤的尋道能力(磁盤的IO)Mysql的自身優化

(Mysql.cnf)文件的優化

2、針對Oracle數據庫而言,Fetch Size 是設定JDBC的Statement讀取數據的時候每次從數據庫中取出的記錄條數,一般設置爲30、50、100.Oracle數據庫的JDBC驅動默認的Fetch Size=15,設置Fetch Size設置爲:30、50,性能會有明顯提升,如果繼續增大,超出100,性能提升不明顯,反而會消耗內存。 
即在Hibernate配製文件中進行配製:


<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>
<prop key="hibernate.show_sql">false</prop>
<!-- Create/update the database tables automatically when the JVM starts up
<prop key="hibernate.hbm2ddl.auto">update</prop> -->
<!-- Turn batching off for better error messages under PostgreSQL 
<prop key="hibernate.jdbc.batch_size">100</prop> -->
<prop key="hibernate.jdbc.batch_size">50</prop>
</props>
</property> 
2、如果是超大的系統,建議生成htm文件。加快頁面提升速度。

3、不要把所有的責任推在hibernate上,對代碼進行重構,減少對數據庫的操作,儘量避免在數據庫查詢時使用in操作,以及避免遞歸查詢操作,代碼質量、系統設計的合理性決定系統性能的高低。

4、 對大數據量查詢時,慎用list()或者iterator()返回查詢結果,

(1)。 使用List()返回結果時,Hibernate會所有查詢結果初始化爲持久化對象,結果集較大時,會佔用很多的處理時間。

(2)。 而使用iterator()返回結果時,在每次調用iterator.next()返回對象並使用對象時,Hibernate才調用查詢將對應的對象初始化,對於大數據量時,每調用一次查詢都會花費較多的時間。當結果集較大,但是含有較大量相同的數據,或者結果集不是全部都會使用時,使用iterator()纔有優勢。

5、在一對多、多對一的關係中,使用延遲加載機制,會使不少的對象在使用時方會初始化,這樣可使得節省內存空間以及減少數據庫的負荷,而且若PO中的集合沒有被使用時,就可減少互數據庫的交互從而減少處理時間。

6、對含有關聯的PO(持久化對象)時,若default-cascade="all"或者 “save-update”,新增PO時,請注意對PO中的集合的賦值操作,因爲有可能使得多執行一次update操作。

7、 對於大數據量新增、修改、刪除操作或者是對大數據量的查詢,與數據庫的交互次數是決定處理時間的最重要因素,減少交互的次數是提升效率的最好途徑,所以在開發過程中,請將show_sql設置爲true,深入瞭解Hibernate的處理過程,嘗試不同的方式,可以使得效率提升。儘可能對每個頁面的顯示,對數據庫的操作減少到100——150條以內。越少越好。

以上是在進行Struts+hibernate+spring進行項目開發中,對hibernate性能優化的幾點心得。


本文依照HIBERNATE幫助文件,一些網絡書籍及項目經驗整理而成,只提供要點和思路,具體做法能留言探討,或是找一些更周詳更有針對性的資料。

  初用HIBERNATE的人也許都遇見過性能問題,實現同一功能,用HIBERNATE和用JDBC性能相差十幾倍非常正常,如果不及早調整,非常可能影響整個項目的進度。

  大體上,對於HIBERNATE性能調優的主要考慮點如下:

  數據庫設計調整

  HQL優化

  API的正確使用(如根據不同的業務類型選用不同的集合及查詢API)

  主設置參數(日誌,查詢緩存,fetch_size, batch_size等)

  映射文件優化(ID生成策略,二級緩存,延遲加載,關聯優化)

  一級緩存的管理

  針對二級緩存,更有許多特有的策略

  事務控制策略。

  1、 數據庫設計

  a) 降低關聯的複雜性

  b) 儘量不使用聯合主鍵

  c) ID的生成機制,不同的數據庫所提供的機制並不完全相同

  d) 適當的冗餘數據,不過分追求高範式

  2、 HQL優化

  HQL如果拋開他同HIBERNATE本身一些緩存機制的關聯,HQL的優化技巧同普通的SQL優化技巧相同,能非常容易在網上找到一些經驗之談。

  3、 主設置

  a) 查詢緩存,同下面講的緩存不太相同,他是針對HQL語句的緩存,即完全相同的語句再次執行時能利用緩存數據。不過,查詢緩存在一個交易系統(數據變更頻繁,查詢條件相同的機率並不大)中可能會起反作用:他會白白耗費大量的系統資源但卻難以派上用場。

  b) fetch_size,同JDBC的相關參數作用類似,參數並不是越大越好,而應根據業務特徵去設置

  c) batch_size同上。

  d) 生產系統中,切記要關掉SQL語句打印。

  4、 緩存

  a) 數據庫級緩存:這級緩存是最高效和安全的,但不同的數據庫可管理的層次並不相同,比如,在ORACLE中,能在建表時指定將整個表置於緩存當中。

  b) SESSION緩存:在一個HIBERNATE SESSION有效,這級緩存的可干預性不強,大多於HIBERNATE自動管理,但他提供清除緩存的方法,這在大批量增加/更新操作是有效的。比如,同時增加十萬條記錄,按常規方式進行,非常可能會發現OutofMemeroy的異常,這時可能需要手動清除這一級緩存:Session.evict及Session.clear

  c) 應用緩存:在一個SESSIONFACTORY中有效,因此也是優化的重中之重,因此,各類策略也考慮的較多,在將數據放入這一級緩存之前,需要考慮一些前提條件:

  i. 數據不會被第三方修改(比如,是否有另一個應用也在修改這些數據?)

  ii. 數據不會太大

  iii. 數據不會頻繁更新(否則使用CACHE可能適得其反)

  iv. 數據會被頻繁查詢

  v. 數據不是關鍵數據(如涉及錢,安全等方面的問題)。

  緩存有幾種形式,能在映射文件中設置:read-only(只讀,適用於非常少變更的靜態數據/歷史數據),nonstrict-read-write,read-write(比較普遍的形式,效率一般),transactional(JTA中,且支持的緩存產品較少)

  d) 分佈式緩存:同c)的設置相同,只是緩存產品的選用不同,在目前的HIBERNATE中可供選擇的不多,oscache, jboss cache,目前的大多數項目,對他們的用於集羣的使用(特別是關鍵交易系統)都持保守態度。在集羣環境中,只利用數據庫級的緩存是最安全的。

  5、 延遲加載

  a) 實體延遲加載:通過使用動態代理實現

  b) 集合延遲加載:通過實現自有的SET/LIST,HIBERNATE提供了這方面的支持

  c) 屬性延遲加載:

  6、 方法選用

  a) 完成同樣一件事,HIBERNATE提供了可供選擇的一些方式,但具體使用什麼方式,可能用性能/代碼都會有影響。顯示,一次返回十萬條記錄(List/Set/Bag/Map等)進行處理,非常可能導致內存不夠的問題,而如果用基於遊標(ScrollableResults)或Iterator的結果集,則不存在這樣的問題。

  b) Session的load/get方法,前者會使用二級緩存,而後者則不使用。

  c) Query和list/iterator,如果去仔細研究一下他們,你可能會發現非常多有意思的情況,二者主要差別(如果使用了Spring,在HibernateTemplate中對應find,iterator方法):

  i. list只能利用查詢緩存(但在交易系統中查詢緩存作用不大),無法利用二級緩存中的單個實體,但list查出的對象會寫入二級緩存,但他一般只生成較少的執行SQL語句,非常多情況就是一條(無關聯)。

  ii. iterator則能利用二級緩存,對於一條查詢語句,他會先從數據庫中找出所有符合條件的記錄的ID,再通過ID去緩存找,對於緩存中沒有的記錄,再構造語句從數據庫中查出,因此非常容易知道,如果緩存中沒有所有符合條件的記錄,使用iterator會產生N+1條SQL語句(N爲符合條件的記錄數)

  iii. 通過iterator,配合緩存管理API,在海量數據查詢中能非常好的解決內存問題,如:

  while(it.hasNext()){

  YouObject object = (YouObject)it.next();

  session.evict(youObject);

  sessionFactory.evice(YouObject.class, youObject.getId());

  }

  如果用list方法,非常可能就出OutofMemory錯誤了。

  iv. 通過上面的說明,我想你應該知道怎麼去使用這兩個方法了。

  7、 集合的選用

  在HIBERNATE 3.1文件的“19.5. Understanding Collection performance”中有周詳的說明。

  8、 事務控制

  事務方面對性能有影響的主要包括:事務方式的選用,事務隔離級別及鎖的選用

  a) 事務方式選用:如果不涉及多個事務管理器事務的話,不必使用JTA,只有JDBC的事務控制就能。

  b) 事務隔離級別:參見標準的SQL事務隔離級別

  c) 鎖的選用:悲觀鎖(一般由具體的事務管理器實現),對於長事務效率低,但安全。樂觀鎖(一般在應用級別實現),如在HIBERNATE中能定義VERSION字段,顯然,如果有多個應用操作數據,且這些應用不是用同一種樂觀鎖機制,則樂觀鎖會失效。因此,針對不同的數據應有不同的策略,同前面許多情況相同,非常多時候我們是在效率和安全/準確性上找一個平衡點,無論怎麼,優化都不是個純技術的問題,你應該對你的應用和業務特徵有足夠的瞭解。

  9、 批量操作

  即使是使用JDBC,在進行大批數據更新時,BATCH和不使用BATCH有效率上也有非常大的差別。我們能通過設置batch_size來讓其支持批量操作。

  舉個例子,要批量刪除某表中的對象,如“delete Account”,打出來的語句,會發現HIBERNATE找出了所有ACCOUNT的ID,再進行刪除,這主要是爲了維護二級緩存,這樣效率肯定高不了,在後續的版本中增加了bulk delete/update,但這也無法解決緩存的維護問題。也就是說,由於有了二級緩存的維護問題,HIBERNATE的批量操作效率並不盡如人意!

  從前面許多要點能看出,非常多時候我們是在效率和安全/準確性上找一個平衡點,無論怎麼,優化都不是個純技術的問題,你應該對你的應用和業務特徵有足夠的瞭解,一般的,優化方案應在架構設計期就基本確定,否則可能導致沒必要的返工,致使項目延期,而作爲架構師和項目經理,還要面對研發人員可能的抱怨,必竟,我們對用戶需求更改的控制力不大,但技術/架構風險是應該在初期意識到並制定好相關的對策。

  更有一點要注意,應用層的緩存只是錦上添花,永遠不要把他當救命稻草,應用的根基(數據庫設計,算法,高效的操作語句,恰當API的選擇等)纔是最重要的。

Hibernate優化方法一:批量修改和刪除 

在Hibernate 2中,如果需要對任何數據進行修改和刪除操作,都需要先執行查詢操作,在得到要修改或者刪除的數據後,再對該數據進行相應的操作處理。在數據量少的情況下采用這種處理方式沒有問題,但需要處理大量數據的時候就可能存在以下的問題: 

◆佔用大量的內存。 

◆需要多次執行update/delete語句,而每次執行只能處理一條數據。 

以上兩個問題的出現會嚴重影響系統的性能。因此,在Hibernate 3中引入了用於批量更新或者刪除數據的HQL語句。這樣,開發人員就可以一次更新或者刪除多條記錄,而不用每次都一個一個地修改或者刪除記錄了。 

如果要刪除所有的User對象(也就是User對象所對應表中的記錄),則可以直接使用下面的HQL語句: 

delete User 

而在執行這個HQL語句時,需要調用Query對象的executeUpdate()方法,具體的實例如下所示: 

String HQL="delete User"; 

Query query=session.createQuery(HQL); 

int size=query.executeUpdate(); 

採用這種方式進行數據的修改和刪除時與直接使用JDBC的方式在性能上相差無幾,是推薦使用的正確方法。 

如果不能採用HQL語句進行大量數據的修改,也就是說只能使用取出再修改的方式時,也會遇到批量插入時的內存溢出問題,所以也要採用上面所提供的處理方法來進行類似的處理。 

Hibernate優化方法二:使用SQL執行批量操作 

在進行批量插入、修改和刪除操作時,直接使用JDBC來執行原生態的SQL語句無疑會獲得最佳的性能,這是因爲在處理的過程中省略或者簡化了以下處理內容: 

● HQL語句到SQL語句的轉換。 

● Java對象的初始化。 

● Java對象的緩存處理。 

但是在直接使用JDBC執行SQL語句時,有一個最重要的問題就是要處理緩存中的Java對象。因爲通過這種底層方式對數據的修改將不能通知緩存去進行相應的更新操作,以保證緩存中的對象與數據庫中的數據是一致的。 

Hibernate優化方法三:提升數據庫查詢的性能 

數據庫查詢性能的提升也是涉及到開發中的各個階段,在開發中選用正確的查詢方法無疑是最基礎也最簡單的。 

1 、SQL語句的優化 

使用正確的SQL語句可以在很大程度上提高系統的查詢性能。獲得同樣數據而採用不同方式的SQL語句在性能上的差距可能是十分巨大的。 

由於Hibernate是對JDBC的封裝,SQL語句的產生都是動態由Hibernate自動完成的。Hibernate產生SQL語句的方式有兩種:一種是通過開發人員編寫的HQL語句來生成,另一種是依據開發人員對關聯對象的訪問來自動生成相應的SQL語句。 

至於使用什麼樣的SQL語句可以獲得更好的性能要依據數據庫的結構以及所要獲取數據的具體情況來進行處理。在確定了所要執行的SQL語句後,可以通過以下三個方面來影響Hibernate所生成的SQL語句: 

◆HQL語句的書寫方法。 

◆查詢時所使用的查詢方法。 

◆對象關聯時所使用的抓取策略。 

2 、使用正確的查詢方法 

在前面已經介紹過,執行數據查詢功能的基本方法有兩種:一種是得到單個持久化對象的get()方法和load()方法,另一種是Query對象的list()方法和iterator()方法。在開發中應該依據不同的情況選用正確的方法。 

get()方法和load()方法的區別在於對二級緩存的使用上。load()方法會使用二級緩存,而get()方法在一級緩存沒有找到的情況下會直接查詢數據庫,不會去二級緩存中查找。在使用中,對使用了二級緩存的對象進行查詢時最好使用load()方法,以充分利用二級緩存來提高檢索的效率。 

list()方法和iterator()方法之間的區別可以從以下幾個方面來進行比較。 

◆執行的查詢不同 

list()方法在執行時,是直接運行查詢結果所需要的查詢語句,而iterator()方法則是先執行得到對象ID的查詢,然後再根據每個ID值去取得所要查詢的對象。因此,對於list()方式的查詢通常只會執行一個SQL語句,而對於iterator()方法的查詢則可能需要執行N+1條SQL語句(N爲結果集中的記錄數)。 

iterator()方法只是可能執行N+1條數據,具體執行SQL語句的數量取決於緩存的情況以及對結果集的訪問情況。 

◆緩存的使用 

list()方法只能使用二級緩存中的查詢緩存,而無法使用二級緩存對單個對象的緩存(但是會把查詢出的對象放入二級緩存中)。所以,除非重複執行相同的查詢操作,否則無法利用緩存的機制來提高查詢的效率。 

iterator()方法則可以充分利用二級緩存,在根據ID檢索對象的時候會首先到緩存中查找,只有在找不到的情況下才會執行相應的查詢語句。所以,緩存中對象的存在與否會影響到SQL語句的執行數量。 

◆對於結果集的處理方法不同 

list()方法會一次獲得所有的結果集對象,而且它會依據查詢的結果初始化所有的結果集對象。這在結果集非常大的時候必然會佔據非常多的內存,甚至會造成內存溢出情況的發生。 

iterator()方法在執行時不會一次初始化所有的對象,而是根據對結果集的訪問情況來初始化對象。因此在訪問中可以控制緩存中對象的數量,以避免佔用過多緩存,導致內存溢出情況的發生。使用iterator()方法的另外一個好處是,如果只需要結果集中的部分記錄,那麼沒有被用到的結果對象根本不會被初始化。所以,對結果集的訪問情況也是調用iterator()方法時執行數據庫SQL語句多少的一個因素。 

所以,在使用Query對象執行數據查詢時應該從以上幾個方面去考慮使用何種方法來執行數據庫的查詢操作。 

Hibernate優化方法四:使用正確的抓取策略 

所謂抓取策略(fetching strategy)是指當應用程序需要利用關聯關係進行對象獲取的時候,Hibernate獲取關聯對象的策略。抓取策略可以在O/R映射的元數據中聲明,也可以在特定的HQL或條件查詢中聲明。 

Hibernate 3定義了以下幾種抓取策略。 

連接抓取(Join fetching) 

連接抓取是指Hibernate在獲得關聯對象時會在SELECT語句中使用外連接的方式來獲得關聯對象。 

查詢抓取(Select fetching) 

查詢抓取是指Hibernate通過另外一條SELECT語句來抓取當前對象的關聯對象的方式。這也是通過外鍵的方式來執行數據庫的查詢。與連接抓取的區別在於,通常情況下這個SELECT語句不是立即執行的,而是在訪問到關聯對象的時候纔會執行。 

子查詢抓取(Subselect fetching) 

子查詢抓取也是指Hibernate通過另外一條SELECT語句來抓取當前對象的關聯對象的方式。與查詢抓取的區別在於它所採用的SELECT語句的方式爲子查詢,而不是通過外連接。 

批量抓取(Batch fetching) 

批量抓取是對查詢抓取的優化,它會依據主鍵或者外鍵的列表來通過單條SELECT語句實現管理對象的批量抓取。 

以上介紹的是Hibernate 3所提供的抓取策略,也就是抓取關聯對象的手段。爲了提升系統的性能,在抓取關聯對象的時機上,還有以下一些選擇。 
立即抓取(Immediate fetching) 

立即抓取是指宿主對象被加載時,它所關聯的對象也會被立即加載。 

延遲集合抓取(Lazy collection fetching) 

延遲集合抓取是指在加載宿主對象時,並不立即加載它所關聯的對象,而是到應用程序訪問關聯對象的時候才抓取關聯對象。這是集合關聯對象的默認行爲。 

延遲代理抓取(Lazy proxy fetching) 

延遲代理抓取是指在返回單值關聯對象的情況下,並不在對其進行get操作時抓取,而是直到調用其某個方法的時候纔會抓取這個對象。 

延遲屬性加載(Lazy attribute fetching) 

延遲屬性加載是指在關聯對象被訪問的時候才進行關聯對象的抓取。 

介紹了Hibernate所提供的關聯對象的抓取方法和抓取時機,這兩個方面的因素都會影響Hibernate的抓取行爲,最重要的是要清楚這兩方面的影響是不同的,不要將這兩個因素混淆,在開發中要結合實際情況選用正確的抓取策略和合適的抓取時機。 

◆抓取時機的選擇 

在Hibernate 3中,對於集合類型的關聯在默認情況下會使用延遲集合加載的抓取時機,而對於返回單值類型的關聯在默認情況下會使用延遲代理抓取的抓取時機。 

對於立即抓取在開發中很少被用到,因爲這很可能會造成不必要的數據庫操作,從而影響系統的性能。當宿主對象和關聯對象總是被同時訪問的時候纔有可能會用到這種抓取時機。另外,使用立即連接抓取可以通過外連接來減少查詢SQL語句的數量,所以,也會在某些特殊的情況下使用。 

然而,延遲加載又會面臨另外一個問題,如果在Session關閉前關聯對象沒有被實例化,那麼在訪問關聯對象的時候就會拋出異常。處理的方法就是在事務提交之前就完成對關聯對象的訪問。 

所以,在通常情況下都會使用延遲的方式來抓取關聯的對象。因爲每個立即抓取都會導致關聯對象的立即實例化,太多的立即抓取關聯會導致大量的對象被實例化,從而佔用過多的內存資源。 

◆抓取策略的選取 

對於抓取策略的選取將影響到抓取關聯對象的方式,也就是抓取關聯對象時所執行的SQL語句。這就要根據實際的業務需求、數據的數量以及數據庫的結構來進行選擇了。 

在這裏需要注意的是,通常情況下都會在執行查詢的時候針對每個查詢來指定對其合適的抓取策略。指定抓取策略的方法如下所示: 

User user = (User) session.createCriteria(User.class) 

.setFetchMode("permissions", FetchMode.JOIN) 

.add( Restrictions.idEq(userId) ) 

.uniqueResult(); 

Hibernate優化方法五:查詢性能提升小結 

在本小節中介紹了查詢性能提升的方法,關鍵是如何通過優化SQL語句來提升系統的查詢性能。查詢方法和抓取策略的影響也是通過執行查詢方式和SQL語句的多少來改變系統的性能的。這些都屬於開發人員所應該掌握的基本技能,避免由於開發不當而導致系統性能的低下。 

在性能調整中,除了前面介紹的執行SQL語句的因素外,對於緩存的使用也會影響系統的性能。通常來說,緩存的使用會增加系統查詢的性能,而降低系統增加、修改和刪除操作的性能(因爲要進行緩存的同步處理)。所以,開發人員應該能夠正確地使用有效的緩存來提高數據查詢的性能,而要避免濫用緩存而導致的系統性能變低。在採用緩存的時候也應該注意調整自己的檢索策略和查詢方法,這三者配合起來纔可以達到最優的性能。 

另外,事務的使用策略也會影響到系統的性能。選取正確的事務隔離級別以及使用。 

在使用Hibernate進行查詢的時候大家都會用到Hibernate緩存,其中Session緩存即一塊內存空間,存放了相互關聯的Java對象,這些位於Session緩存中的對象就是持久化對象,Session根據持久化對象的狀態變化來同步更新數據庫。這個Session緩存是Hibernate的一級緩存。此外,SessionFactory有一個內置緩存和一個外置緩存,即Hibernate的第二級緩存。而Hibernate正是由於這些緩存的存在,才使得其數據庫操作效率提高,就是說,在提供了方便易操作的操作數據庫數據的方式的同時保證了工作效率,但是不能因此而免去後顧之憂,需要在設計業務邏輯層的時候考慮使用最優的架構,節省有效的系統資源。在查詢方面,Hibernate主要從以下幾個方面來優化查詢性能:

1.降低訪問數據庫的頻率,減少select語句的數目。實現手段包括:

    使用迫切左外連接或迫切內連接檢索策略。
    對延遲檢索或立即檢索策略設置批量檢索數目。
    使用查詢緩存。

2.避免多餘加載程序不需要訪問的數據。實現手段包括:

    使用延遲檢索策略。
    使用集合過濾。

3.避免報表查詢數據佔用緩存。實現手段爲利用投影查詢功能,查詢出實體的部分屬性。

4.減少select語句中的字段,從而降低訪問數據庫的數據量。實現手段爲利用Query的iterate()方法。

    在插入和更新數據時,要控制insert和update語句,合理設置映射屬性來保證插入更新的性能,例如,當表中包含許多字段時,建議把dynamic-update屬性和dynamic-update屬性都設爲true,這樣在insert和update語句中就只包含需要插入或更新的字段,這可以節省數據庫執行SQL語句的時間,從而提高應用的運行性能。

發佈了70 篇原創文章 · 獲贊 3 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章