lifejoy網(wǎng)友寫了段測(cè)試程序,用Hibernate作為持久手段測(cè)試了大數(shù)據(jù)量寫入MySql數(shù)據(jù)庫(kù)的性能。程序主要使用了一個(gè)循環(huán)嵌套,最里層循 環(huán)為批量插入記錄的代碼,每一批插1000條記錄,最外層循環(huán)為批次的控制,一共循環(huán)100批次,這樣總的數(shù)據(jù)寫入量為1000x100共十萬(wàn)記錄。從 lifejoy的測(cè)試數(shù)據(jù)看,用JDBC直接寫的速率是600-800條/秒,而用Hibernate寫的速率會(huì)從一開始的300多條降至幾十條每秒,這 個(gè)差距非常之大,難怪lifejoy使用了“暴差”這一非常使人觸目驚心的語(yǔ)言。



Hibernate 的寫入性能到底如何?真的到了“暴差”這樣的地步么?其性能與JDBC直寫相比,到底差距多大?這些個(gè)問題,通過google 結(jié)果,眾說紛紜,莫衷一是,在臺(tái)灣JavaWorld論壇上,有網(wǎng)友貼出了Hibernate比JDBC性能更加優(yōu)越的測(cè)試結(jié)果分析圖,也有很多網(wǎng)友在詬 病Hibernate在ORM的同時(shí)喪失了性能,到底真相在何方?由于今年做了一個(gè)基于Oracle的大型系統(tǒng),需要支撐高并發(fā)數(shù)據(jù)訪問量,在決定系統(tǒng)架 構(gòu)的時(shí)候,首席架構(gòu)師選擇了iBatis,而放棄了Hibernate,其中一個(gè)最大的考慮就是這個(gè)性能因素,可惜當(dāng)初沒有進(jìn)行技術(shù)實(shí)際論證,于是有了今 天的這個(gè)“考”,打算通過實(shí)際測(cè)試結(jié)果來驗(yàn)證一下Hibernate的性能情況,以澄清如下問題:

<!--[if !supportLists]-->1. <!--[endif]-->Hibernate ORM讀寫與JDBC方式讀寫在性能上孰優(yōu)孰劣?

<!--[if !supportLists]-->2. <!--[endif]-->優(yōu)勢(shì)多少?劣勢(shì)又是幾何?



依照l(shuí)ifejoy的思路下寫以下一段代碼:



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創(chuàng)建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入

//lh.createPersons(10, 1000, 100);

//用jdbc直接創(chuàng)建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入

lh.createPersonsByJDBC(10, 1000,100);

}





//用hibernate創(chuàng)建person記錄, loopNum為循環(huán)插入的次數(shù),batchNum1為每次循環(huán)插入的記錄數(shù),batchNum2為物理批量插入記錄數(shù)

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 ) {//執(zhí)行物理批量插入

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創(chuàng)建person記錄, loopNum為循環(huán)插入的次數(shù),batchNum1為每次循環(huán)插入的記錄數(shù),batchNum2為物理批量插入記錄數(shù)

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){//執(zhí)行物理批量插入

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數(shù)據(jù)庫(kù)環(huán)境

private void setup(){

config = new Configuration().configure();

sessionFactory = config.buildSessionFactory();

session = sessionFactory.openSession();

}



//銷毀hibernate數(shù)據(jù)庫(kù)環(huán)境

private void teardown(){

session.close();

sessionFactory.close();

}

}






測(cè) 試環(huán)境主要為:J2SDK1.4.2_04, MySql4.1.9-Max, Hibernate3.1, IBM Thinkpad R32-P4 1.8G, 512M Memory;MySql中待插表的類型為INNODB,以支持事務(wù),ISAM類型表的讀寫速率要遠(yuǎn)高于INNODB,這里不采用ISAM是因?yàn)椴恢С质? 務(wù)。



主要分為三個(gè)測(cè)試場(chǎng)景,以下為三個(gè)場(chǎng)景的測(cè)試記錄和分析:



測(cè)試場(chǎng)景一:

############# 測(cè)試環(huán)境一 #######################

mysql版本:4.1.9-max

jdbc驅(qū)動(dòng):mysql-connector-java-3.1.11-bin.jar

hibernate: 3.1



################################################



1.hibernate批量插入,創(chuàng)建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作

測(cè)試記錄:

======================================================================

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



測(cè)試結(jié)果:

hibernate新記錄創(chuàng)建平均速率:~290條/秒

======================================================================



2.jdbc批量插入,創(chuàng)建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作

測(cè)試記錄:

======================================================================

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



測(cè)試結(jié)果:

jdbc新記錄創(chuàng)建平均速率:~1147條/秒

======================================================================





******測(cè)試環(huán)境一結(jié)論:jdbc性能明顯優(yōu)于hibernate,寫入速率比jdbc/hibernate=3.95








測(cè)試場(chǎng)景二:

############# 測(cè)試環(huán)境二 #######################

mysql版本:4.1.9-max

jdbc驅(qū)動(dòng):mysql-connector-java-3.0.11-bin.jar(注意這里更換了mysql的connectorJ驅(qū)動(dòng)?。。。?

hibernate: 3.1

################################################



1.hibernate批量插入,創(chuàng)建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作

測(cè)試記錄:

======================================================================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



測(cè)試結(jié)果:

新記錄創(chuàng)建平均速率:~974條/秒

======================================================================



2.jdbc批量插入,創(chuàng)建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作

測(cè)試記錄:

======================================================================

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



測(cè)試結(jié)果:

新記錄創(chuàng)建平均速率:~1497條/秒

======================================================================

******測(cè)試環(huán)境二結(jié)論:jdbc性能仍優(yōu)于hibernate,寫入速率比jdbc/hibernate =1.58






測(cè)試場(chǎng)景三:

############# 測(cè)試環(huán)境三 #######################

mysql版本:4.1.9-max

jdbc驅(qū)動(dòng):mysql-connector-java-3.0.11-bin.jar(與測(cè)試環(huán)境二使用同樣的驅(qū)動(dòng))

hibernate: 3.1

特別說明:記錄插入不使用事務(wù)

################################################



1.jdbc批量插入,創(chuàng)建10000條記錄,分10次插入,每次1000條,每100條記錄做一次批量插入操作,不使用事務(wù)(注意這里,不使用事務(wù)?。。?

測(cè)試記錄:

===========================================================================================

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



測(cè)試結(jié)果:

新記錄創(chuàng)建平均速率:~50條/秒

======================================================================




測(cè)試結(jié)果分析:

1. 在同等測(cè)試環(huán)境和條件下,hibernate優(yōu)于jdbc這種說法是錯(cuò)誤的,從測(cè)試結(jié)果來看, jdbc要優(yōu)于hibernate,這從理論上是可以理解的,hibernate的基礎(chǔ)就是jdbc,它不可能優(yōu)于jdbc。

2. 影響數(shù)據(jù)庫(kù)操作性能的因素很多,主要包括:

1)數(shù)據(jù)庫(kù)自身

如mysql表類型,是ISAM還是innodb

2)數(shù)據(jù)庫(kù)驅(qū)動(dòng)

從 測(cè)試數(shù)據(jù)和結(jié)果看,mysql的3.0.11版本的驅(qū)動(dòng)顯然更適合于mysql4.1.9版本的數(shù)據(jù)庫(kù),而高版本的3.1.11用于 hibernate的插入操作則會(huì)喪失近3.5倍的執(zhí)行效率,另外,經(jīng)過筆者測(cè)試,在3.1.11版本的驅(qū)動(dòng)中,使用與不使用批次(batch)插入操作 居然沒有任何區(qū)別,這也能解釋一些技術(shù)論壇上提到的hibernate批處理操作有時(shí)候會(huì)實(shí)效這個(gè)令人困惑的問題。

3)操作數(shù)據(jù)庫(kù)的程序本身

測(cè)試環(huán)境3表明,當(dāng)mysql的表類型為innodb時(shí),即使是采用JDBC直接寫的方式,不采用事務(wù)方式插入記錄,寫入速率幾乎是“蝸速”(~50條/秒),這可以說是“殺手級(jí)”的因素了。



結(jié)論:

<!--[if !supportLists]-->1. 筆者估計(jì)在大數(shù)據(jù)量寫入狀況下,Hibernate的性能損失在30%-35%左右<!--[endif]-->

<!--[if !supportLists]-->2. 對(duì)于要獲取高性能數(shù)據(jù)讀寫的系統(tǒng),不推薦使用Hibernate的ORM方式進(jìn)行數(shù)據(jù)讀寫。<!--[endif]-->

<!--[if !supportLists]-->3. 性能的優(yōu)劣除了所采用的技術(shù)決定外,更取決于使用技術(shù)的人,比如在測(cè)試環(huán)境三中,不采用事務(wù)方式寫數(shù)據(jù),其速度簡(jiǎn)直不能以“暴差”來形容,想想這樣一種情 況,讓你去開一輛法拉利F1賽車,你一定能想象得到你駕駛的速度。:)<!--[endif]-->



后記:

在 進(jìn)行測(cè)試的時(shí)候,起初筆者使用的JDBC驅(qū)動(dòng)是J/Conncector 3.1.11版本,發(fā)現(xiàn)Hibernate的批量寫設(shè)置根本不起作用,是否使用批量寫根本就沒有差別,在一些網(wǎng)站上面也發(fā)現(xiàn)有類似的疑問,經(jīng)過更換為 3.0.x版本驅(qū)動(dòng)后,批量寫才生效,而且無論是Hibernate方式還是JDBC方式下,寫記錄的性能明顯提升,表明3.0.X的驅(qū)動(dòng)更適合于 MySql4.1,為什么高版本的3.1.11反而在低版本數(shù)據(jù)庫(kù)上面表現(xiàn)出低效?筆者在安裝Roller這個(gè)Apache孵化器blog項(xiàng)目的時(shí)候,也 對(duì)安裝指導(dǎo)中推薦使用3.0.X版本來匹配MySql4.1數(shù)據(jù)庫(kù)這個(gè)問題比較疑惑,可惜Roller的InstallGuid沒有做具體解釋,感興趣的 網(wǎng)友可以到Roller網(wǎng)站的wiki上去弄清楚這個(gè)問題,并把答案做個(gè)回復(fù),非常感謝。這個(gè)插曲還說明了一個(gè)道理——“升級(jí)并非總是好事”。

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1339954