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