http://community.csdn.net/Expert/TopicView3.asp?id=5025307
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();???????? ?????? } }
|
測試環境主要為:
J2SDK
主要分為三個測試場景,以下為三個場景的測試記錄和分析:
測試場景一:
############# 測試環境一 #######################
mysql
版本:
jdbc
驅動:
mysql-connector-java- 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
版本:
jdbc
驅動:
mysql-connector-java- 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
版本:
jdbc
驅動:
mysql-connector-java- 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) 操作數據庫的程序本身
測試環境 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