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.  对于要获取高性能数据读写的系统,不推荐使用HibernateORM方式进行数据读写。<!--[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