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