lifejoy網(wǎng)友寫(xiě)了段測(cè)試程序,用Hibernate作為持久手段測(cè)試了大數(shù)據(jù)量寫(xiě)入MySql數(shù)據(jù)庫(kù)的性能。程序主要使用了一個(gè)循環(huán)嵌套,最里層循 環(huán)為批量插入記錄的代碼,每一批插1000條記錄,最外層循環(huán)為批次的控制,一共循環(huán)100批次,這樣總的數(shù)據(jù)寫(xiě)入量為1000x100共十萬(wàn)記錄。從 lifejoy的測(cè)試數(shù)據(jù)看,用JDBC直接寫(xiě)的速率是600-800條/秒,而用Hibernate寫(xiě)的速率會(huì)從一開(kāi)始的300多條降至幾十條每秒,這 個(gè)差距非常之大,難怪lifejoy使用了“暴差”這一非常使人觸目驚心的語(yǔ)言。
Hibernate 的寫(xiě)入性能到底如何?真的到了“暴差”這樣的地步么?其性能與JDBC直寫(xiě)相比,到底差距多大?這些個(gè)問(wèn)題,通過(guò)google 結(jié)果,眾說(shuō)紛紜,莫衷一是,在臺(tái)灣JavaWorld論壇上,有網(wǎng)友貼出了Hibernate比JDBC性能更加優(yōu)越的測(cè)試結(jié)果分析圖,也有很多網(wǎng)友在詬 病Hibernate在ORM的同時(shí)喪失了性能,到底真相在何方?由于今年做了一個(gè)基于Oracle的大型系統(tǒng),需要支撐高并發(fā)數(shù)據(jù)訪問(wèn)量,在決定系統(tǒng)架 構(gòu)的時(shí)候,首席架構(gòu)師選擇了iBatis,而放棄了Hibernate,其中一個(gè)最大的考慮就是這個(gè)性能因素,可惜當(dāng)初沒(méi)有進(jìn)行技術(shù)實(shí)際論證,于是有了今 天的這個(gè)“考”,打算通過(guò)實(shí)際測(cè)試結(jié)果來(lái)驗(yàn)證一下Hibernate的性能情況,以澄清如下問(wèn)題:
<!--[if !supportLists]-->1. <!--[endif]-->Hibernate ORM讀寫(xiě)與JDBC方式讀寫(xiě)在性能上孰優(yōu)孰劣?
<!--[if !supportLists]-->2. <!--[endif]-->優(yōu)勢(shì)多少?劣勢(shì)又是幾何?
依照l(shuí)ifejoy的思路下寫(xiě)以下一段代碼:
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類型表的讀寫(xiě)速率要遠(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,寫(xiě)入速率比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,寫(xiě)入速率比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
特別說(shuō)明:記錄插入不使用事務(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這種說(shuō)法是錯(cuò)誤的,從測(cè)試結(jié)果來(lái)看, 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)過(guò)筆者測(cè)試,在3.1.11版本的驅(qū)動(dòng)中,使用與不使用批次(batch)插入操作 居然沒(méi)有任何區(qū)別,這也能解釋一些技術(shù)論壇上提到的hibernate批處理操作有時(shí)候會(huì)實(shí)效這個(gè)令人困惑的問(wèn)題。
3)操作數(shù)據(jù)庫(kù)的程序本身
測(cè)試環(huán)境3表明,當(dāng)mysql的表類型為innodb時(shí),即使是采用JDBC直接寫(xiě)的方式,不采用事務(wù)方式插入記錄,寫(xiě)入速率幾乎是“蝸速”(~50條/秒),這可以說(shuō)是“殺手級(jí)”的因素了。
結(jié)論:
<!--[if !supportLists]-->1. 筆者估計(jì)在大數(shù)據(jù)量寫(xiě)入狀況下,Hibernate的性能損失在30%-35%左右<!--[endif]-->
<!--[if !supportLists]-->2. 對(duì)于要獲取高性能數(shù)據(jù)讀寫(xiě)的系統(tǒng),不推薦使用Hibernate的ORM方式進(jìn)行數(shù)據(jù)讀寫(xiě)。<!--[endif]-->
<!--[if !supportLists]-->3. 性能的優(yōu)劣除了所采用的技術(shù)決定外,更取決于使用技術(shù)的人,比如在測(cè)試環(huán)境三中,不采用事務(wù)方式寫(xiě)數(shù)據(jù),其速度簡(jiǎn)直不能以“暴差”來(lái)形容,想想這樣一種情 況,讓你去開(kāi)一輛法拉利F1賽車,你一定能想象得到你駕駛的速度。:)<!--[endif]-->
后記:
在 進(jìn)行測(cè)試的時(shí)候,起初筆者使用的JDBC驅(qū)動(dòng)是J/Conncector 3.1.11版本,發(fā)現(xiàn)Hibernate的批量寫(xiě)設(shè)置根本不起作用,是否使用批量寫(xiě)根本就沒(méi)有差別,在一些網(wǎng)站上面也發(fā)現(xiàn)有類似的疑問(wèn),經(jīng)過(guò)更換為 3.0.x版本驅(qū)動(dòng)后,批量寫(xiě)才生效,而且無(wú)論是Hibernate方式還是JDBC方式下,寫(xiě)記錄的性能明顯提升,表明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版本來(lái)匹配MySql4.1數(shù)據(jù)庫(kù)這個(gè)問(wèn)題比較疑惑,可惜Roller的InstallGuid沒(méi)有做具體解釋,感興趣的 網(wǎng)友可以到Roller網(wǎng)站的wiki上去弄清楚這個(gè)問(wèn)題,并把答案做個(gè)回復(fù),非常感謝。這個(gè)插曲還說(shuō)明了一個(gè)道理——“升級(jí)并非總是好事”。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1339954