問題描述
-
我開發的網站加了個新功能:需要在線上處理表數據的批量合並和更新,昨天下午發布上線,執行該功能后,服務器的load突然增高,變化曲線異常,SA教育了我一番,讓我盡快處理,將CPU負載降低。
-
工作所需,我經常要寫些程序批量處理數據,每次執行幾十萬數據處理的時候,我機子的CPU都會飆高,而且數據處理速度會越來越慢。比如第一個1W條要5分鍾,第二個1W條就要10分鍾,要干其他事情的時候機子也卡的不行,只能等着處理完數據。
其實我一直認為是數據量太大,從來不認為是程序問題,所以一直沒怎么關注過。這次問題浮上表面,所以要好好解決下!
產生原因
主要原因:Hibernate的一級緩存影響。
我們每次保存的東西都會保存在Session緩存中,這就是Hibernate的一級緩存,如果我們一直循環執行save等操作,緩存里東西會越來越多,速度也就越來越慢,服務器一直在循環處理,自然也會增加負載。
這本來就是Hibernate不擅長的地方,而且一級緩存不可以不用,如果我們要保存的數據量十分巨大,那么在程序中執行添加、更新方法時,Session對象自身開辟的一級緩存會不斷消耗,直至OutOfMemoryError (內存溢出異常)。
這就需要我們管理好Hibernate的緩存,或者不使用Hibernate。
解決方案
批量插入優化
1、仍舊用Hibernate API來進行批處理,但在一定的量的時候,及時的清除緩存。
1)優化Hibernate,在配置文件中設置hibernate.jdbc.batch_size參數,來指定每次提交SQL的數量。配置hibernate.jdbc.batch_size參數的原因就是盡量少讀數據庫,hibernate.jdbc.batch_size參數值越大,讀數據庫的次數越少,速度越快。
<!--設置hibernate.jdbc.batch_size參數--> <hibernate-configuration> <session-factory> ......... <property name="hibernate.jdbc.batch_size">50</property> ......... <session-factory> <hibernate-configuration>
2)程序及時清除緩存,即每插入一定量的數據后及時把它們從內部緩存中清除掉,釋放占用的內存。 Session實現了異步write-behind,它允許Hibernate顯式地寫操作的批處理。
示例代碼:
// 每處理50條清空緩存 session.save(myObject); if (i/50 == 0) { session.flush(); session.clear(); } // 在我的項目中寫法如下: if (i/50 == 0) { this.getHibernateTemplate().flush(); this.getHibernateTemplate().clear(); }
2、通過JDBC API來做批量插入,繞過Hibernate API。這個方法性能上是最好的,也是最快的。
示例代碼:
String insertSql = "insert into user(name,address) values(?,?)"; Session session = getHibernateTemplate().getSessionFactory().openSession(); Connection conn = session.connection(); PrepareStatement stmt = conn.prepareStatement(insertSql); // 方式1:自動提交 conn.setAutoCommit(true); for(int i = 0; i++; i<10000) { stmt.setString(1, "testName"); stmt.setString(2, "testAddress"); stmt.execute(); } // 方式2:批量提交 conn.setAutoCommit(false); for(int i = 0; i++; i<10000) { stmt.setString(1, "testName"); stmt.setString(2, "testAddress"); stmt.addBatch(); if (i % 100 == 0) { stmt.executeBatch(); conn.commit(); } } stmt.executeBatch(); conn.commit(); // 關閉session session.close();
附測試數據:
// 測試方法:循環插入10000條數據,拆分10頁,每頁1000條。 // 直接Hibernate的save()方法,不做任何處理。 page 0 process time : 5925 page 1 process time : 6722 page 2 process time : 8019 page 3 process time : 9456 page 4 process time : 10263 page 5 process time : 11511 page 6 process time : 12988 page 7 process time : 13969 page 8 process time : 15196 page 9 process time : 16820 // Hibernate的save()方法,但每1個清除緩存。 page 0 process time : 10257 page 1 process time : 10709 page 2 process time : 11223 page 3 process time : 10595 page 4 process time : 10990 page 5 process time : 10222 page 6 process time : 10453 page 7 process time : 10196 page 8 process time : 9645 page 9 process time : 10295 // Hibernate的save()方法,但每50個清除緩存。 page 0 process time : 5848 page 1 process time : 5480 page 2 process time : 5739 page 3 process time : 5960 page 4 process time : 6287 page 5 process time : 5947 page 6 process time : 7012 page 7 process time : 6235 page 8 process time : 6063 page 9 process time : 6055 // JDBC的auto commit 方式 page 0 process time : 840 page 1 process time : 800 page 2 process time : 800 page 3 process time : 847 page 4 process time : 806 page 5 process time : 829 page 6 process time : 1042 page 7 process time : 893 page 8 process time : 857 page 9 process time : 854 // JDBC的batch方式,每50個commit page 