该帖已经被评为良好帖
|
|||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||||||||||||||||
发表时间:2010-09-23
最后修改:2010-11-11
---------作者2010-9-25日补充----------------------------------------------------------------- 首先感谢javaeye的朋友,积极的参与讨论交流本帖子。
交流的结果: MySql连接的url中要加rewriteBatchedStatements参数,例如 String connectionUrl="jdbc:mysql://192.168.1.100:3306/test?rewriteBatchedStatements=true"; 还要保证mysql JDBC驱的版本。MySql的JDBC驱动的批量插入操作性能是很优秀的。
具体的分析见后面srdrm 的回复。
------------------------------------------------------------------------------------------------- 事情的起因: mercyblitz 写道
elf8848 写道
前面有多位朋友提出,让楼主使用 jdbc的批量操作,就是PreparedStatement 类上的addBatch(),executeBatch()方法。
在这里要提醒一下大家,MySql的JDBC驱动,是不支持批量操作的,就算你在代码中调用了批量操作的方法,MySql的JDBC驱动,也是安一般操作来处理的。 同样Fetch Size特性MySql的JDBC驱动也不支持。而Oracle的JDBC驱动是都支持的。 楼主使用的是Mysql数据库, 不要指望通过批处理来提高 性能了。 请不要想当然,建议你去看一下MySQL JDBC的源代码! MySQL JDBC驱动在发送命令是,都是传递一个数组的String类型,然后转化为一个二维byte数组。 如果是一条的Statement的话,这个String数组只有一个元素,如果是Batch的话,则有相应个元素。 最后发送IO命令。不清楚你的结论是哪里来的?
表只有两个字段: id int uname varchar(10)
下面是测试代码: package jdbc2; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class Mysql { public static void main(String[] args) { test_mysql(); //test_mysql_batch(); //test_oracle(); //test_oracle_batch(); } /** * mysql非批量插入10万条记录 * 第1次:17437 ms * 第2次:17422 ms * 第3次:17046 ms */ public static void test_mysql(){ String url="jdbc:mysql://192.168.10.139:3306/test"; String userName="root"; String password="1234"; Connection conn=null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(url, userName, password); conn.setAutoCommit(false); String sql = "insert into t_user(id,uname) values(?,?)"; PreparedStatement prest = conn.prepareStatement(sql); long a=System.currentTimeMillis(); for(int x = 0; x < 100000; x++){ prest.setInt(1, x); prest.setString(2, "张三"); prest.execute(); } conn.commit(); long b=System.currentTimeMillis(); System.out.println("MySql非批量插入10万条记录用时"+ (b-a)+" ms"); } catch (Exception ex) { ex.printStackTrace(); }finally{ try { if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * mysql批量插入10万条记录 * 第1次:17437 ms * 第2次:17562 ms * 第3次:17140 ms */ public static void test_mysql_batch(){ String url="jdbc:mysql://192.168.10.139:3306/test"; String userName="root"; String password="1234"; Connection conn=null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(url, userName, password); conn.setAutoCommit(false); String sql = "insert into t_user(id,uname) values(?,?)"; PreparedStatement prest = conn.prepareStatement(sql); long a=System.currentTimeMillis(); for(int x = 0; x < 100000; x++){ prest.setInt(1, x); prest.setString(2, "张三"); prest.addBatch(); } prest.executeBatch(); conn.commit(); long b=System.currentTimeMillis(); System.out.println("MySql批量插入10万条记录用时"+ (b-a)+" ms"); } catch (Exception ex) { ex.printStackTrace(); }finally{ try { if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * oracle非批量插入10万条记录 * 第1次:22391 ms * 第2次:22297 ms * 第3次:22703 ms */ public static void test_oracle(){ String url="jdbc:oracle:thin:@192.168.10.139:1521:orcl"; String userName="scott"; String password="tiger"; Connection conn=null; try { Class.forName("oracle.jdbc.OracleDriver"); conn = DriverManager.getConnection(url, userName, password); conn.setAutoCommit(false); String sql = "insert into t_user(id,uname) values(?,?)"; PreparedStatement prest = conn.prepareStatement(sql); long a=System.currentTimeMillis(); for(int x = 0; x < 100000; x++){ prest.setInt(1, x); prest.setString(2, "张三"); prest.execute(); } conn.commit(); long b=System.currentTimeMillis(); System.out.println("Oracle非批量插入10万记录用时"+ (b-a)+" ms"); conn.close(); } catch (Exception ex) { ex.printStackTrace(); }finally{ try { if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * oracle批量插入10万条记录 * 第1次:360 ms * 第2次:328 ms * 第3次:359 ms */ public static void test_oracle_batch(){ String url="jdbc:oracle:thin:@192.168.10.139:1521:orcl"; String userName="scott"; String password="tiger"; Connection conn=null; try { Class.forName("oracle.jdbc.OracleDriver"); conn = DriverManager.getConnection(url, userName, password); conn.setAutoCommit(false); String sql = "insert into t_user(id,uname) values(?,?)"; PreparedStatement prest = conn.prepareStatement(sql); long a=System.currentTimeMillis(); for(int x = 0; x < 100000; x++){ prest.setInt(1, x); prest.setString(2, "张三"); prest.addBatch(); } prest.executeBatch(); conn.commit(); long b=System.currentTimeMillis(); System.out.println("Oracle批量插入10万记录用时"+ (b-a)+" ms"); conn.close(); } catch (Exception ex) { ex.printStackTrace(); }finally{ try { if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-23
最后修改:2010-09-23
说一下我的测试情况,代码就是照搬LZ,只有mysql,没测oracle
台式机windows xp sp3 e8400 4g内存 完全本机测试,127.0.0.1 mysql版本5.1.50 jdbc版本-5.1.13 jdk版本1_5_0_22 每插一次前都truncate table 非批量8800-9200左右,大量出现88xx 92xx 90xx这种 批量时间会多50-100 LZ代码是10W后才executeBatch,我试过每1000或1W条executeBatch,也就是if(x%1000==0)executeBatch一下,还是没区别 |
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-23
http://www.iteye.com/topic/49129
\ 咳咳,这个问题07年就讨论过了。~ |
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-23
kimmking 写道 http://www.iteye.com/topic/49129
\ 咳咳,这个问题07年就讨论过了。~ 07年那个讨论的是PreparedStatement Statement哪个快 楼主想证明的只是mysql jdbc不支持批量操作而已 |
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-23
jl580650 写道 kimmking 写道 http://www.iteye.com/topic/49129
\ 咳咳,这个问题07年就讨论过了。~ 07年那个讨论的是PreparedStatement Statement哪个快 楼主想证明的只是mysql jdbc不支持批量操作而已 翻翻后面的帖子,有各种测试。有mysql官方文档的引用和解释。 |
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-23
elf8848 写道
事情的起因:
我给你举个例子,MySQL-5.1.7 JDBC驱动,一般获取的底层Connection实现为com.mysql.jdbc.JDBC4Connection,它是com.mysql.jdbc.ConnectionImpl的父类。
ConnectionImpl 4109行代码:
public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { checkClosed(); ..... } else { pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); // 不加设置,默认PreparedStatement实例在这里返回。 } return pStmt; }
其实现类为:
public class JDBC4PreparedStatement extends PreparedStatement
其Batch操作的实现:
/** * JDBC 2.0 Add a set of parameters to the batch. * * @exception SQLException * if a database-access error occurs. * * @see StatementImpl#addBatch */ public void addBatch() throws SQLException { if (this.batchedArgs == null) { this.batchedArgs = new ArrayList(); } this.batchedArgs.add(new BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull)); }
前面我说过,
mercyblitz 写道
elf8848 写道
前面有多位朋友提出,让楼主使用 jdbc的批量操作,就是PreparedStatement 类上的addBatch(),executeBatch()方法。
在这里要提醒一下大家,MySql的JDBC驱动,是不支持批量操作的,就算你在代码中调用了批量操作的方法,MySql的JDBC驱动,也是安一般操作来处理的。 同样Fetch Size特性MySql的JDBC驱动也不支持。而Oracle的JDBC驱动是都支持的。 楼主使用的是Mysql数据库, 不要指望通过批处理来提高 性能了。 请不要想当然,建议你去看一下MySQL JDBC的源代码! MySQL JDBC驱动在发送命令是,都是传递一个数组的String类型,然后转化为一个二维byte数组。 如果是一条的Statement的话,这个String数组只有一个元素,如果是Batch的话,则有相应个元素。 最后发送IO命令。不清楚你的结论是哪里来的?
好,接下来看一下实现BatchParams:
BatchParams(byte[][] strings, InputStream[] streams, boolean[] isStreamFlags, int[] lengths, boolean[] isNullFlags) { // // Make copies // this.parameterStrings = new byte[strings.length][]; this.parameterStreams = new InputStream[streams.length]; this.isStream = new boolean[isStreamFlags.length]; this.streamLengths = new int[lengths.length]; this.isNull = new boolean[isNullFlags.length];
然后executeBatch操作:
public int[] executeBatch() throws SQLException { checkClosed(); if (this.connection.isReadOnly()) { throw new SQLException(Messages.getString("PreparedStatement.25") //$NON-NLS-1$ + Messages.getString("PreparedStatement.26"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } synchronized (this.connection.getMutex()) { if (this.batchedArgs == null || this.batchedArgs.size() == 0) { return new int[0]; } // we timeout the entire batch, not individual statements int batchTimeout = this.timeoutInMillis; this.timeoutInMillis = 0; resetCancelledState(); try { clearWarnings(); if (!this.batchHasPlainStatements && this.connection.getRewriteBatchedStatements()) { if (canRewriteAsMultivalueInsertStatement()) { return executeBatchedInserts(batchTimeout); } if (this.connection.versionMeetsMinimum(4, 1, 0) // MySQL > 4.1.0 && !this.batchHasPlainStatements && this.batchedArgs != null && this.batchedArgs.size() > 3 /* cost of option setting rt-wise */) { return executePreparedBatchAsMultiStatement(batchTimeout); } } return executeBatchSerially(batchTimeout); } finally { clearBatch(); } } }
如果你有兴趣的话,方法会调用到protected int[] executeBatchSerially(int batchTimeout) throws SQLException 方法
-> protected int executeUpdate(byte[][] batchedParameterStrings, InputStream[] batchedParameterStreams, boolean[] batchedIsStream, int[] batchedStreamLengths, boolean[] batchedIsNull, boolean isReallyBatch) throws SQLException {
-> protected ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, Buffer sendPacket, boolean createStreamingResultSet, boolean queryIsSelectOnly, Field[] metadataFromCache, boolean isBatch)
- > Connectionimpl.execSQL(StatementImpl callingStatement, String sql, int maxRows, Buffer packet, int resultSetType, int resultSetConcurrency, boolean streamResults, String catalog, Field[] cachedMetadata, boolean isBatch) throws SQLException {
-> com.mysql.jdbc.MysqlIO.sqlQueryDirect(StatementImpl callingStatement, String query, String characterEncoding, Buffer queryPacket, int maxRows, int resultSetType, int resultSetConcurrency, boolean streamResults, String catalog, Field[] cachedMetadata) throws Exception
-> MyqlIO.private final void send(Buffer packet, int packetLen) throws SQLException
Buffer 会保存传出的byte数组,最终commit的时候,把数据通过IO传输。当然如果你的Buffer的长度,超过了max_allowed_packet的话,会报出一场。 |
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-23
elf8848 写道
测试方法:
你的结论中,没有错,MySQL在处理Batch的方法不是很好,包括PreparedStatement处理传输的都是整句,而不是仅为参数部分,所以结果不理想,不过Batch处理会提高IO的效率。
|
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-23
最后面补充一句,楼主还可以通过
DataBaseMetaData的方法来判断 boolean java.sql.DatabaseMetaData.supportsBatchUpdates() throws SQLException |
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-24
最后修改:2010-09-24
你可以给基金会建议下给mysql加个批量处理数据的方法,或者做下优化。
|
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||
发表时间:2010-09-24
最后修改:2010-09-24
kimmking 写道 jl580650 写道 kimmking 写道 http://www.iteye.com/topic/49129
\ 咳咳,这个问题07年就讨论过了。~ 07年那个讨论的是PreparedStatement Statement哪个快 楼主想证明的只是mysql jdbc不支持批量操作而已 翻翻后面的帖子,有各种测试。有mysql官方文档的引用和解释。 kimmking也来了,我真是荣幸啊,呵呵。 我去看了你说的文章《PreparedStatement真的比Statement快吗?》 。 有好多朋友都是2007年以后加入javaeye的,我也是, 呵呵。 |
|||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||