9 月 17 日 在 CSDN 社区的 Java 技术栏中看到了网友 lifejoy 的一个贴子,题为“ hibernate 写入性能暴差,如何配置?”,详细链接请见

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();        

       }

}

 

 

测试环境主要为: 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 上去弄清楚这个问题,并把答案做个回复,非常感谢。这个插曲还说明了一个道理——“升级并非总是好事”。


只有注册用户登录后才能发表评论。


网站导航:
 

posts - 41, comments - 7, trackbacks - 0, articles - 0

Copyright © weibogao