【Java/JDBC.ORM】 jdbc插入大量数据时优化处理

转自:http://blog.csdn.net/xiashan17/article/details/6049587


这个就是做个练习.想看下JDBC的最大优化度.
我的要求就是插入到数据库里大量的数据 比如10W 或者 100W 而且要求内存稳定.
首先说下我的代码:
我的数据库MySQL 

[java]  view plain copy
  1. CREATE TABLE `users` (  
  2.   `id` int(11) NOT NULL auto_increment,  
  3.   `firstname` varchar(50) NOT NULL,  
  4.   `lastname` varchar(50) NOT NULL,  
  5.   `age` int(11) NOT NULL,  
  6.   PRIMARY KEY  (`id`)  
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8  

我的数据库辅助类

[java]  view plain copy
  1. package com.jdbc.batch;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.DriverManager;  
  5. import java.sql.PreparedStatement;  
  6. import java.sql.ResultSet;  
  7. import java.sql.SQLException;  
  8.   
  9. public final class DBUtils {  
  10.     private static String mysqlurl = "jdbc:mysql://localhost:3306/mytest";  
  11.     private static String accessurl = "jdbc:mysql://localhost:3306/mytest";  
  12.     private static String user = "root";  
  13.     private static String password = "root";  
  14.   
  15.     // 获得连接  
  16.     public static Connection getAccessConn() throws SQLException {  
  17.         return DriverManager.getConnection(accessurl, user, password);  
  18.     }  
  19.       
  20.     public static Connection getMySqlConn() throws SQLException {  
  21.         return DriverManager.getConnection(mysqlurl, user, password);  
  22.     }  
  23.   
  24.     // 释放连接  
  25.     public static void free(ResultSet rs, PreparedStatement ps, Connection conn) {  
  26.         try {  
  27.             if (rs != null) {  
  28.                 rs.close();  
  29.             }  
  30.         } catch (SQLException e) {  
  31.             e.printStackTrace();  
  32.         } finally {  
  33.             try {  
  34.                 if (ps != null) {  
  35.                     ps.close();  
  36.                 }  
  37.             } catch (SQLException e) {  
  38.                 e.printStackTrace();  
  39.             } finally {  
  40.   
  41.                 try {  
  42.                     if (conn != null) {  
  43.                         conn.close();  
  44.                     }  
  45.                 } catch (SQLException e) {  
  46.                     e.printStackTrace();  
  47.                 }  
  48.   
  49.             }  
  50.   
  51.         }  
  52.   
  53.     }  
  54.   
  55.     // 加载驱动  
  56.     static {  
  57.         try {  
  58.             Class.forName("com.mysql.jdbc.Driver");  
  59.         } catch (ClassNotFoundException e) {  
  60.             System.out.println("驱动加载出错");  
  61.         }  
  62.     }  
  63.   
  64. }  

测试类

 

[java]  view plain copy
  1. package com.jdbc.batch;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.PreparedStatement;  
  5. import java.sql.ResultSet;  
  6. import java.sql.SQLException;  
  7.   
  8. public class BatchExample {  
  9.     private static Connection mysqlConn = null;  
  10.     private static ResultSet rs = null;  
  11.     // 总条数  
  12.     private static int allCount = 10000;  
  13.     // 分批条数  
  14.     private static int preCount = 1000;  
  15.     // 计数器  
  16.     private static int count = 0;  
  17.   
  18.     private static String insertSQL = "insert into users(firstname, lastname, age) values(?, ?, ?)";  
  19.   
  20.     private static PreparedStatement mysqlPs = null;  
  21.   
  22.     public static void main(String[] args) throws SQLException {  
  23.         try {  
  24.             mysqlConn = DBUtils.getMySqlConn();  
  25.             mysqlPs = mysqlConn.prepareStatement(insertSQL);  
  26.             mysqlConn.setAutoCommit(false);  
  27.             long start = System.currentTimeMillis();  
  28.               
  29.             for (int i = 1; i <= allCount; i++) {  
  30.                 mysqlPs.setString(1"firstname" + i);  
  31.                 mysqlPs.setString(2"lastname" + i);  
  32.                 mysqlPs.setInt(323);  
  33.                 mysqlPs.addBatch();          
  34.                 if ((i % preCount) == 0){  
  35.                     mysqlPs.executeBatch();  
  36.                     System.out.println("当前进行完毕===>" + (++count) * preCount + "条");  
  37.                 }  
  38.             }          
  39.             long end = System.currentTimeMillis();  
  40.             System.out.println("数据导入完毕,所用时间为: " + (end - start) + " ms");  
  41.   
  42.         } catch (Exception e) {  
  43.             mysqlConn.rollback();  
  44.             System.out.println("数据出错,已进行回滚");  
  45.             throw new RuntimeException();  
  46.         } finally {  
  47.             mysqlConn.commit();  
  48.             DBUtils.free(rs, mysqlPs, mysqlConn);  
  49.         }  
  50.     }  
  51.   
  52. }  

 

我用的批处理.最后执行10000条的记录是

 

当前进行完毕===>1000条
当前进行完毕===>2000条
当前进行完毕===>3000条
当前进行完毕===>4000条
当前进行完毕===>5000条
当前进行完毕===>6000条
当前进行完毕===>7000条
当前进行完毕===>8000条
当前进行完毕===>9000条
当前进行完毕===>10000条
数据导入完毕,所用时间为: 8140 ms

 

而我换了一种方案 用executeUpdate一次提交.SQL用StringBuilder串接 效率提升很快.
代码

[c-sharp]  view plain copy
  1. package com.jdbc.batch;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.PreparedStatement;  
  5. import java.sql.ResultSet;  
  6. import java.sql.SQLException;  
  7.   
  8. public class BufferQuery {  
  9.     private static Connection mysqlConn = null;  
  10.     private static ResultSet rs = null;  
  11.     // 总条数  
  12.     private static int allCount = 10000;  
  13.     // 分批条数  
  14.     private static int preCount = 1000;  
  15.     // 计数器  
  16.     private static int count = 0;  
  17.   
  18.     private static String insertSQL = "insert into users(firstname, lastname, age) values(?, ?, ?)";  
  19.   
  20.     private static PreparedStatement mysqlPs = null;  
  21.   
  22.     public static void main(String[] args) throws SQLException {  
  23.         try {  
  24.             StringBuilder sb = new StringBuilder();  
  25.             sb.append("insert into users(firstname, lastname, age) values");  
  26.             mysqlConn = DBUtils.getMySqlConn();  
  27.             mysqlPs = mysqlConn.prepareStatement(insertSQL);  
  28.             mysqlConn.setAutoCommit(false);  
  29.             long start = System.currentTimeMillis();  
  30.               
  31.             for (int i = 1; i <= allCount; i++) {  
  32.                 if(i > 1) sb.append(",");  
  33.                 sb.append("('aa"+ i +"','bb',23)");      
  34.   
  35.                 if(i % preCount == 0){  
  36.                     System.out.println("导入进行===>" + (++count * preCount) + "条");                  
  37.                 }                  
  38.             }  
  39.             mysqlPs.executeUpdate(sb.toString());  
  40.               
  41.               
  42.             long end = System.currentTimeMillis();  
  43.             System.out.println("数据导入完毕,所用时间为: " + (end - start) + " ms");  
  44.   
  45.         } catch (Exception e) {  
  46.             mysqlConn.rollback();  
  47.             System.out.println("数据出错,已进行回滚");  
  48.             throw new RuntimeException();  
  49.         } finally {  
  50.             mysqlConn.commit();  
  51.             DBUtils.free(rs, mysqlPs, mysqlConn);  
  52.         }  
  53.     }  
  54.   
  55. }  

 

[java]  view plain copy
  1. // 用的StringBuilder串接  
  2.            long start = System.currentTimeMillis();  
  3.               
  4.             for (int i = 1; i <= allCount; i++) {  
  5.                 if(i > 1) sb.append(",");  
  6.                 sb.append("('aa"+ i +"','bb',23)");      
  7.   
  8.                 if(i % preCount == 0){  
  9.                     System.out.println("导入进行===>" + (++count * preCount) + "条");                  
  10.                 }                  
  11.             }  
  12.             mysqlPs.executeUpdate(sb.toString());  

 

导入进行===>1000条
导入进行===>2000条
导入进行===>3000条
导入进行===>4000条
导入进行===>5000条
导入进行===>6000条
导入进行===>7000条
导入进行===>8000条
导入进行===>9000条
导入进行===>10000条
数据导入完毕,所用时间为: 219 ms

1W条才129ms为什么会比批处理快这么多.但是还有问题就是 如果数据量更大 如20W 那么StringBuilder就装不下 堆栈溢出....

stringbuilder 看了msdn,发现最大的长度是Int32,开始没有理解,后来HJ告诉我能够至少容纳3M的长度,我又去check了一下msdn,最大的长度是2的32次方(2的32次方就是4G),但是网上有人说是2G,那就是2的31次方。

 

要注意的是stringbuilder非线程安全  用再多线程处理时要慎用

 

 


 

 

我测试的jdbc批处理操作,插入112万条数据的对比
批量向数据表插入一条数据(包含一个float值) 
建立表个数 每个表插入数据条数 单个数据表耗时(毫秒) 数据库连接耗时(毫秒) 带数据库连接总耗时(毫秒) 不带数据库连接总耗时(毫秒) 备注
800 1440 480-600 312 437703 437391 调用Statement.execute批量插入
800 1440 100-200 328 118985 118657 调用Statement.addBatch批量插入
800 1440 40-110 313 56922 56609 调用PreparedStatement.addBatch批量插入

 

 

 

使用jdbc向数据库插入100000条记录,分别使用statement,PreparedStatement,及PreparedStatement+批处理3种方式进行测试:

  1、使用statement插入100000条记录

  public void exec(Connection conn){

  try {

  Long beginTime = System.currentTimeMillis();

  conn.setAutoCommit(false);//设置手动提交

  Statement st = conn.createStatement();

  for(int i=0;i<100000;i++){

  String sql="insert into t1(id) values ("+i+")";

  st.executeUpdate(sql);

  }

  Long endTime = System.currentTimeMillis();

  System.out.println("st:"+(endTime-beginTime)/1000+"秒");//计算时间

  st.close();

  conn.close();

  } catch (SQLException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  2、使用PreparedStatement对象

  public void exec2(Connection conn){

  try {

  Long beginTime = System.currentTimeMillis();

  conn.setAutoCommit(false);//手动提交

  PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

  for(int i=0;i<100000;i++){

  pst.setInt(1, i);

  pst.execute();

  }

  conn.commit();

  Long endTime = System.currentTimeMillis();

  System.out.println("pst:"+(endTime-beginTime)/1000+"秒");//计算时间

  pst.close();

  conn.close();

  } catch (SQLException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

wordend 相关阅读:

  • 性能测试(并发负载压力)测试分析
  • 软件性能测试的重要性及策略
  • 软件性能测试入门

  3、使用PreparedStatement + 批处理

  public void exec3(Connection conn){

  try {

  conn.setAutoCommit(false);

  Long beginTime = System.currentTimeMillis();

  PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

  for(int i=1;i<=100000;i++){

  pst.setInt(1, i);

  pst.addBatch();

  if(i%1000==0){//可以设置不同的大小;如50,100,500,1000等等

  pst.executeBatch();

  conn.commit();

  pst.clearBatch();

  }

  }

  Long endTime = System.currentTimeMillis();

  System.out.println("pst+batch:"+(endTime-beginTime)/1000+"秒");

  pst.close();

  conn.close();

  } catch (SQLException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  在Oracle 10g中测试,结果:

  1、使用statement耗时142秒;

  2、使用PreparedStatement耗时56秒;

  3、使用PreparedStatement + 批处理耗时:

  a.50条插入一次,耗时5秒;

  b.100条插入一次,耗时2秒;

  c.1000条以上插入一次,耗时1秒;

  通过以上可以得出结论,在使用jdbc大批量插入数据时,明显使用第三种方式(PreparedStatement + 批处理)性能更优。

  当使用sqlserver 2000进行测试时,第三种方式最少耗时5秒,从这方面可以看出Oracle在处理大量数据时,明显性能更强。

 


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值