lifejoy網友寫了段測試程序,用Hibernate作為持久手段測試了大數據量寫入MySql數據庫的性能。程序主要使用了一個循環嵌套,最里層循
環為批量插入記錄的代碼,每一批插1000條記錄,最外層循環為批次的控制,一共循環100批次,這樣總的數據寫入量為1000x100共十萬記錄。從
lifejoy的測試數據看,用JDBC直接寫的速率是600-800條/秒,而用Hibernate寫的速率會從一開始的300多條降至幾十條每秒,這
個差距非常之大,難怪lifejoy使用了“暴差”這一非常使人觸目驚心的語言。 Hibernate
的寫入性能到底如何?真的到了“暴差”這樣的地步么?其性能與JDBC直寫相比,到底差距多大?這些個問題,通過google
結果,眾說紛紜,莫衷一是,在臺灣JavaWorld論壇上,有網友貼出了Hibernate比JDBC性能更加優越的測試結果分析圖,也有很多網友在詬
病Hibernate在ORM的同時喪失了性能,到底真相在何方?由于今年做了一個基于Oracle的大型系統,需要支撐高并發數據訪問量,在決定系統架
構的時候,首席架構師選擇了iBatis,而放棄了Hibernate,其中一個最大的考慮就是這個性能因素,可惜當初沒有進行技術實際論證,于是有了今
天的這個“考”,打算通過實際測試結果來驗證一下Hibernate的性能情況,以澄清如下問題: <!--[if !supportLists]-->1. <!--[endif]-->Hibernate ORM讀寫與JDBC方式讀寫在性能上孰優孰劣? <!--[if !supportLists]-->2. <!--[endif]-->優勢多少?劣勢又是幾何? 依照lifejoy的思路下寫以下一段代碼: package com.gmail.newmanhuang.learnhibernate; import java.util.Iterator; import java.util.List; import org.hibernate.SessionFactory; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.Criteria; import org.hibernate.criterion.Expression; import com.gmail.newmanhuang.learnhibernate.model.Person; import java.sql.*; public class LearnHibernateMain { private Configuration config; private SessionFactory sessionFactory; private Session session; public static void main(String[] args) { LearnHibernateMain lh=new LearnHibernateMain(); //用hibernate創建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入 //lh.createPersons(10, 1000, 100); //用jdbc直接創建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入 lh.createPersonsByJDBC(10, 1000,100); } //用hibernate創建person記錄, loopNum為循環插入的次數,batchNum1為每次循環插入的記錄數,batchNum2為物理批量插入記錄數 private void createPersons(int loopNum,int batchNum1,int batchNum2){ setup(); System.out.println("hibernate record creating testing.\r\n" + "loop times:" + loopNum + "\r\nbatch number:" + batchNum1); for(int i=0;i<loopNum;i++){ try { Thread.sleep(50);//休眠 } catch (InterruptedException e) { e.printStackTrace(); } long fPoint=System.currentTimeMillis(); Transaction tx = session.beginTransaction(); for(int j=0;j<batchNum1;j++){ Person person = new Person(); person.setName("name-" + i +"-"+ j); person.setAge(new Integer(25)); session.save(person); //batch flush if ( j % batchNum2 == 0 ) {//執行物理批量插入 session.flush(); session.clear(); } } tx.commit(); long tPoint=System.currentTimeMillis(); //打印插入batchNum1條記錄的速率(條/秒) System.out.println( "the " + i + " batch" + "(" + batchNum1 +") rcds/s:" + ((double)batchNum1/(tPoint-fPoint))*1000); } teardown(); } //用jdbc創建person記錄, loopNum為循環插入的次數,batchNum1為每次循環插入的記錄數,batchNum2為物理批量插入記錄數 private void createPersonsByJDBC(int loopNum,int batchNum1,int batchNum2){ System.out.println("JDBC record creating testing.\r\n" + "loop times:" + loopNum + "\r\nbatch number:" + batchNum1); Connection conn=getDBConn(); try{ PreparedStatement pstmt=conn.prepareStatement("insert into person(name,age) values(?,?)"); for(int i=0;i<loopNum;i++){ try { Thread.sleep(50);//休眠 } catch (InterruptedException e) { e.printStackTrace(); } long fPoint=System.currentTimeMillis(); conn.setAutoCommit(false); for(int j=0;j<batchNum1;j++){ String name="name-" + i +"-"+ j; pstmt.setString(1, name); pstmt.setInt(2, 25); pstmt.addBatch(); if(j%batchNum2==0){//執行物理批量插入 pstmt.executeBatch(); conn.commit(); } } pstmt.executeBatch(); conn.commit(); conn.setAutoCommit(true); long tPoint=System.currentTimeMillis(); //打印插入batchNum1條記錄的速率(條/秒) System.out.println( "the " + i + " batch" + "(" + batchNum1 +") rcds/s:" + ((double)batchNum1/(tPoint-fPoint))*1000); } pstmt.close(); }catch(Exception x){ try{ conn.close(); }catch(Exception x1){ } } } //獲取JDBC連接 private Connection getDBConn(){ Connection conn=null; try { Class.forName("org.gjt.mm.mysql.Driver"); conn=DriverManager.getConnection("jdbc:mysql://localhost/learnhibernate", "root", ""); } catch (Exception x) { } return conn; } //初始化hibernate數據庫環境 private void setup(){ config = new Configuration().configure(); sessionFactory = config.buildSessionFactory(); session = sessionFactory.openSession(); } //銷毀hibernate數據庫環境 private void teardown(){ session.close(); sessionFactory.close(); } } 測
試環境主要為:J2SDK1.4.2_04, MySql4.1.9-Max, Hibernate3.1, IBM Thinkpad R32-P4
1.8G, 512M
Memory;MySql中待插表的類型為INNODB,以支持事務,ISAM類型表的讀寫速率要遠高于INNODB,這里不采用ISAM是因為不支持事
務。 主要分為三個測試場景,以下為三個場景的測試記錄和分析: 測試場景一: ############# 測試環境一 ####################### mysql版本:4.1.9-max jdbc驅動:mysql-connector-java-3.1.11-bin.jar hibernate: 3.1 ################################################ 1.hibernate批量插入,創建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作 測試記錄: ====================================================================== hibernate record creating testing. loop times:10 batch number:1000 the 0 batch(1000) rcds/s:172.1763085399449 the 1 batch(1000) rcds/s:214.73051320592657 the 2 batch(1000) rcds/s:302.6634382566586 the 3 batch(1000) rcds/s:321.13037893384717 the 4 batch(1000) rcds/s:318.9792663476874 the 5 batch(1000) rcds/s:316.05562579013906 the 6 batch(1000) rcds/s:318.9792663476874 the 7 batch(1000) rcds/s:317.05770450221945 the 8 batch(1000) rcds/s:317.9650238473768 the 9 batch(1000) rcds/s:314.96062992125985 測試結果: hibernate新記錄創建平均速率:~290條/秒 ====================================================================== 2.jdbc批量插入,創建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作 測試記錄: ====================================================================== JDBC record creating testing. loop times:10 batch number:1000 the 0 batch(1000) rcds/s:812.3476848090983 the 1 batch(1000) rcds/s:988.1422924901185 the 2 batch(1000) rcds/s:1233.0456226880394 the 3 batch(1000) rcds/s:1314.060446780552 the 4 batch(1000) rcds/s:1201.923076923077 the 5 batch(1000) rcds/s:1349.527665317139 the 6 batch(1000) rcds/s:853.9709649871904 the 7 batch(1000) rcds/s:1218.026796589525 the 8 batch(1000) rcds/s:1175.0881316098707 the 9 batch(1000) rcds/s:1331.5579227696405 測試結果: jdbc新記錄創建平均速率:~1147條/秒 ====================================================================== ******測試環境一結論:jdbc性能明顯優于hibernate,寫入速率比jdbc/hibernate=3.95 測試場景二: ############# 測試環境二 ####################### mysql版本:4.1.9-max jdbc驅動:mysql-connector-java-3.0.11-bin.jar(注意這里更換了mysql的connectorJ驅動!!!) hibernate: 3.1 ################################################ 1.hibernate批量插入,創建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作 測試記錄: ======================================================================hibernate record creating testing. loop times:10 batch number:1000 the 0 batch(1000) rcds/s:536.7686527106817 the 1 batch(1000) rcds/s:504.28643469490675 the 2 batch(1000) rcds/s:1062.6992561105205 the 3 batch(1000) rcds/s:1122.334455667789 the 4 batch(1000) rcds/s:1133.7868480725624 the 5 batch(1000) rcds/s:1122.334455667789 the 6 batch(1000) rcds/s:1008.0645161290322 the 7 batch(1000) rcds/s:1085.7763300760043 the 8 batch(1000) rcds/s:1074.1138560687434 the 9 batch(1000) rcds/s:1096.4912280701756 測試結果: 新記錄創建平均速率:~974條/秒 ====================================================================== 2.jdbc批量插入,創建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作 測試記錄: ====================================================================== JDBC record creating testing. loop times:10 batch number:1000 the 0 batch(1000) rcds/s:1231.527093596059 the 1 batch(1000) rcds/s:1406.4697609001407 the 2 batch(1000) rcds/s:2000.0 the 3 batch(1000) rcds/s:1692.047377326565 the 4 batch(1000) rcds/s:1386.9625520110958 the 5 batch(1000) rcds/s:1349.527665317139 the 6 batch(1000) rcds/s:1074.1138560687434 the 7 batch(1000) rcds/s:1386.9625520110958 the 8 batch(1000) rcds/s:1636.6612111292961 the 9 batch(1000) rcds/s:1814.8820326678765 測試結果: 新記錄創建平均速率:~1497條/秒 ====================================================================== ******測試環境二結論:jdbc性能仍優于hibernate,寫入速率比jdbc/hibernate =1.58 測試場景三: ############# 測試環境三 ####################### mysql版本:4.1.9-max jdbc驅動:mysql-connector-java-3.0.11-bin.jar(與測試環境二使用同樣的驅動) hibernate: 3.1 特別說明:記錄插入不使用事務 ################################################ 1.jdbc批量插入,創建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作,不使用事務(注意這里,不使用事務!!) 測試記錄: =========================================================================================== JDBC record creating testing. loop times:10 batch number:1000 the 0 batch(1000) rcds/s:43.11645755184754 the 1 batch(1000) rcds/s:34.32651379925854 the 2 batch(1000) rcds/s:40.65701740120345 the 3 batch(1000) rcds/s:62.44925997626928 the 4 batch(1000) rcds/s:69.58942240779402 the 5 batch(1000) rcds/s:42.45743641998896 the 6 batch(1000) rcds/s:44.420753375977256 the 7 batch(1000) rcds/s:44.44049417829527 the 8 batch(1000) rcds/s:56.63797009515179 the 9 batch(1000) rcds/s:71.73601147776183 測試結果: 新記錄創建平均速率:~50條/秒 ====================================================================== 測試結果分析: 1. 在同等測試環境和條件下,hibernate優于jdbc這種說法是錯誤的,從測試結果來看, jdbc要優于hibernate,這從理論上是可以理解的,hibernate的基礎就是jdbc,它不可能優于jdbc。 2. 影響數據庫操作性能的因素很多,主要包括: 1)數據庫自身 如mysql表類型,是ISAM還是innodb 2)數據庫驅動 從
測試數據和結果看,mysql的3.0.11版本的驅動顯然更適合于mysql4.1.9版本的數據庫,而高版本的3.1.11用于
hibernate的插入操作則會喪失近3.5倍的執行效率,另外,經過筆者測試,在3.1.11版本的驅動中,使用與不使用批次(batch)插入操作
居然沒有任何區別,這也能解釋一些技術論壇上提到的hibernate批處理操作有時候會實效這個令人困惑的問題。 3)操作數據庫的程序本身 測試環境3表明,當mysql的表類型為innodb時,即使是采用JDBC直接寫的方式,不采用事務方式插入記錄,寫入速率幾乎是“蝸速”(~50條/秒),這可以說是“殺手級”的因素了。 結論: <!--[if !supportLists]-->1. 筆者估計在大數據量寫入狀況下,Hibernate的性能損失在30%-35%左右<!--[endif]--> <!--[if !supportLists]-->2. 對于要獲取高性能數據讀寫的系統,不推薦使用Hibernate的ORM方式進行數據讀寫。<!--[endif]--> <!--[if
!supportLists]-->3.
性能的優劣除了所采用的技術決定外,更取決于使用技術的人,比如在測試環境三中,不采用事務方式寫數據,其速度簡直不能以“暴差”來形容,想想這樣一種情
況,讓你去開一輛法拉利F1賽車,你一定能想象得到你駕駛的速度。:)<!--[endif]--> 后記: 在
進行測試的時候,起初筆者使用的JDBC驅動是J/Conncector
3.1.11版本,發現Hibernate的批量寫設置根本不起作用,是否使用批量寫根本就沒有差別,在一些網站上面也發現有類似的疑問,經過更換為
3.0.x版本驅動后,批量寫才生效,而且無論是Hibernate方式還是JDBC方式下,寫記錄的性能明顯提升,表明3.0.X的驅動更適合于
MySql4.1,為什么高版本的3.1.11反而在低版本數據庫上面表現出低效?筆者在安裝Roller這個Apache孵化器blog項目的時候,也
對安裝指導中推薦使用3.0.X版本來匹配MySql4.1數據庫這個問題比較疑惑,可惜Roller的InstallGuid沒有做具體解釋,感興趣的
網友可以到Roller網站的wiki上去弄清楚這個問題,并把答案做個回復,非常感謝。這個插曲還說明了一個道理——“升級并非總是好事”。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1339954