- 浏览: 7188852 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (397)
- 架构研究 (44)
- 网络协议-TCP/HTTP (20)
- SPRING (23)
- HIBERNATE 3 (30)
- STRUTS 2 (20)
- Java/J2se (25)
- Servlet/Jsp (9)
- 开发工具IDE (10)
- WEB服务器 (17)
- 前端--Java Script (33)
- 前端--CSS (7)
- OS--Linux (31)
- OS--MAC OS (19)
- MySQL数据库 (19)
- Oracle数据库 (11)
- 项目管理工具 (12)
- 数据报表技术 (4)
- 图像处理技术 (10)
- 其它综合技术 (13)
- 权限管理 (1)
- MyBatis (1)
- 网络安全 (6)
- IO (21)
- PostgreSQL (2)
- Eclipse RCP (1)
- GWT (1)
- 算法 (3)
- Storm (2)
- zookeeper (3)
- 消息 (1)
最新评论
-
bukebuhao:
已解决,打开文件太多。ulimit -n 上次临时生效后再次s ...
TFS(Taobao File System)配置dataServer、分区、挂载数据盘 -
bukebuhao:
ds重启后启动不了。报错信息如下,请大神帮助[2018-08- ...
TFS(Taobao File System)配置dataServer、分区、挂载数据盘 -
墨上清:
感谢,看完有了一个完整的知识网络。
Spring MVC 教程,快速入门,深入分析 -
云卷云舒灬:
谢谢,学到很多
Spring MVC 教程,快速入门,深入分析 -
luozhy:
非常感谢非常感谢
Project Web Access ActiveX控件 安装
MySql 的批量操作,要加rewriteBatchedStatements参数
作者:赵磊
博客:http://elf8848.iteye.com
--------------------------------结论 ---------------------------------
MySql 非批量 10万条记录, 5700条/秒
MySql 批量(batch) 10万条记录, 62500条/秒
oracle 非批量插入10万条记录, 4464 条/秒
oracle 批量 (batch)插入10万条记录, 27778 条/秒
注:以上测试都是在插入10万条数据完成之后,一次性提交事务(对性能影响很大,占了很大便宜)。
另有一篇文章,说明提交事务的次数对insert性能的影响:《MySql 插入(insert)性能测试 》
MySql的JDBC连接的url中要加rewriteBatchedStatements参数,并保证5.1.13以上版本的驱动,才能实现高性能的批量插入。
例如: String connectionUrl="jdbc:mysql://192.168.1.100:3306/test?rewriteBatchedStatements=true" ;
还要保证Mysql JDBC驱的版本。MySql的JDBC驱动的批量插入操作性能是很优秀的。
Mysql JDBC驱动,各个版本测试结果:
MySql JDBC 驱动版本号
插入10万条数据用时
5.0.8
加了rewriteBatchedStatements参数,没有提高还是17.4秒
5.1.7
加了rewriteBatchedStatements参数,没有提高还是17.4秒
5.1.13
加了rewriteBatchedStatements参数,插入速度提高到1.6秒
关于rewriteBatchedStatements参数,Mysql官方的说明:
Should the driver use multiqueries (irregardless of the setting of "allowMultiQueries") as well as rewriting of prepared statements for INSERT into multi-value inserts when executeBatch() is called? Notice that this has the potential for SQL injection if using plain java.sql.Statements and your code doesn't sanitize input correctly. Notice that for prepared statements, server-side prepared statements can not currently take advantage of this rewrite option, and that if you don't specify stream lengths when using PreparedStatement.set*Stream(), the driver won't be able to determine the optimum number of parameters per batch and you might receive an error from the driver that the resultant packet is too large. Statement.getGeneratedKeys() for these rewritten statements only works when the entire batch includes INSERT statements.
请参见:http://dev.mysql.com/doc/refman/5.1/en/connector-j-reference-configuration-properties.html
-------------------------事情的起因 ---------------------------------
原贴是《使用DBCP 数据库连接池遇到的两个比较怀疑的问题》
http://www.iteye.com/topic/768416
帖子是问连接池问题的,主要是使用连接池向MySql插入大量数据的事儿,有很多javaEye的朋友让作者使用JDBC的批量操作来提高大量插入数据的性能。
在这里要提醒一下大家,MySql的JDBC驱动,是不支持批量操作的,就算你在代码中调用了批量操作的方法,MySql的JDBC驱动,也是按一般insert操作来处理的。
同样Fetch Size特性MySql的JDBC驱动也不支持。而Oracle的JDBC驱动是都支持的。
楼主使用的是Mysql数据库, 不要指望通过批处理来提高 性能了。
请不要想当然,建议你去看一下MySQL JDBC的源代码!
MySQL JDBC驱动在发送命令是,都是传递一个数组的String类型,然后转化为一个二维byte数组。
如果是一条的Statement的话,这个String数组只有一个元素,如果是Batch的话,则有相应个元素。
最后发送IO命令。不清楚你的结论是哪里来的?
我在这里重复的提醒大家:
MySql的JDBC驱动,不是真正支持批量操作的,就算你在代码中调用了批量操作的方法,MySql的JDBC驱动也是按照一般操作来处理的。
这不是什么重大发现,也不是什么新消息,是老调重弹,如果你使用Mysql数据库不要寄希望于通过JDBC批量操作来提高大量插入数据的性能,起码目前的MySql的JDBC驱动是这样的。
--------------------------------- 测试环境 ---------------------------------
测试机:笔记本电脑ThinkPad T400
操作系统:Windows XP
CPU:P8600 2.4G
内存:2G
IP:192.168.10.124
数据库服务器:笔记本ThinkPad T400
操作系统:虚拟机Windows 2003
内存:操作系统分配了500M (真可怜啊)
IP:192.168.10.139
数据库服务器上安装有MySql5.0.18 ,Oracle10G,都是默认安装。
JDBC驱动版本:
MySql : mysql-connector-java-5.0.8
Oracle : ojdbc6.jar (之前使用ojdbc14.jar批量插入10万条,实际只插入了3万多条,其它的丢失了,换ojdbc6.jar后,一次commit插入100万条也没有问题)
表只有两个字段:
id int
uname varchar(10)
innoDB引擎
---------------------------------测试结果: ---------------------------------
mysql非批量插入10万条记录 |
mysql批量插入10万条记录 (JDBC的URL中未加参数) |
oracle非批量插入10万条记录 | oracle批量插入10万条记录 | |
第1次 | 17437 ms | 17437 ms | 22391 ms | 360 ms |
第2次 | 17422 ms | 17562 ms | 22297 ms | 328 ms |
第3次 | 17046 ms | 17140 ms | 22703 ms | 359 ms |
这里通过一个点也可以看出来Mysql批量与非批量性能是一样。
oracle的JDBC实现的批量操作的性能十分优秀。
---------------------------------测试方法: ---------------------------------
下面是测试代码:
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万条记录(未加rewriteBatchedStatements参数) ,加了参数后是1600ms左右 * 第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 url="jdbc:mysql://127.0.0.1:3306/dev?rewriteBatchedStatements=true";
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(); } } } }
评论
我这里提点注意:插入语句要采用INSET INTO ... VALUES(...)形式,不能写成INSERT INTO ... VALUE(...),否则是rewriteBatchedStatements不生效。。我试了半天就是因为同事用的INSERT INTO VALUE,一直未生效。
另外插入语句最后不要加分号,否则会报错。
我学习了!
先说结果:
按楼主的代码原封不动,在我的机器上执行情况是 8000 毫秒左右,
优化后,非batch 方式保持不变,batch 的方式 1000毫秒
楼主的测试结果让人非常失望...
但mysql名气这么大,不太相信在这样的问题上会出现与oracle这么大的差距。
直觉告诉我,应该是一些简单的设置问题,事实上最后得到的结果也是如此的。
带着解决这个疑惑的想法,依据大家之前得到的一些结果,信息,开始测试
工具:
eclipse-3.6, mysql-5.1.48, mysql-jdbc-driver 5.1.11, mysql workbench
前面说过了,我直觉认为代码不会有问题,所以先着手改善mysql 的服务器配置,innodb的设置。
改了几个参数,都没有什么效果。加大了日志缓存,只是提高到7000多毫秒。最后甚至很多歪门邪道的设置都大胆用了,一度让mysql 无法启动。。。最终都收效甚微,这个步骤大概试了将近一个小时。
这条路看来是走不通了。。得寻找别的方法
冷静下来想想,其实从代码中应该是可以发现些端倪
楼主的非batch代码中,每次调用 execute() 其实是会通过网络发送一条语句到服务器端的,是不会在客户端排队攒着的。
因为这个方法必须返回一个结果。它必然跟服务器发生了一次交互。
而在batch处理的代码中,其addBatch 就是无返回值,它提供了一个可能就是在客户端将语句缓存排队攒着,最后executeBatch时才发送到服务器端。
用代码可以证明,在batch处理方法的代码中,在 executeBatch, 及 commit 方法执行前,分别安插两条打印时间语句:
System.out.println("before executeBatch. "+ (System.currentTimeMillis()-a)+" ms"); prest.executeBatch(); System.out.println("before commit. "+ (System.currentTimeMillis()-a)+" ms"); conn.commit();
在我机器上的结果是,
before executeBatch. 279 ms before commit. 7922 ms MySql批量插入10万条记录用时7923 ms
说明客户端在攒语句时,相当的快,279毫秒就完成了,但在 executeBatch 这个方法的调用过程中,花费了 7920 减 去 279 的毫秒数。大部分都耗在这里了。 最后提交事务非常快,1毫秒而已
想想看,前边说过,非batch和batch的处理几乎是一样的时间。
可不可以先假设 batch 的方式与非batch一样,每一条insrt语句事实上均是单独发往服务器的呢?
浏览下源代码吧。
好几位兄弟都描述了源代码,直接从那几个类入手吧,事实上关键的类是这个 com.mysql.jdbc.PreparedStatement
先看了其中的 addBatch 方法,没有任何问题,只是将语句添加进入一个 List 中保存。
那么 executeBatch 呢?
再贴一下吧, 关键看其中的这部分,顺带说一下, 这个mysql-jdbcdriver的源代码是 5.1.13的
try { clearWarnings(); if (!this.batchHasPlainStatements && this.connection.getRewriteBatchedStatements()) { if (canRewriteAsMultiValueInsertAtSqlLevel()) { return executeBatchedInserts(batchTimeout); //执行路径之一 } if (this.connection.versionMeetsMinimum(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(); }
其实最终,executeBatch 的执行路径有三种可能。代码中我已标出来
不小心按了提交了,继续编辑此回复吧。
代码不算太复杂,但是有一个参数能帮助我们更快的确定mysql的batch工作机制,那就是
mysql jdbc driver 的connection url, 其中有一个参数是: rewriteBatchedStatements
完整的参数参考看这里:http://ftp.ntu.edu.tw/ftp/pub/MySQL/doc/refman/5.1/en/connector-j-reference-configuration-properties.html
rewriteBatchedStatements 参数默认为false, 需要手工设置为true,设置方式大概像这样:
String connectionUrl="jdbc:mysql://192.168.1.100:3306/test?rewriteBatchedStatements=true";
默认时候,rewriteBatchedStatements=false时,执行路径会跳到 executeBatchSerially,此方法内部将语句一条条发送,与非batch处理简直一样,所以慢,就在这里了。
当设为 true时,会执行executeBatchedInserts方法,事实上mysql支持这样的插入语句
insert into t_user(id,uname) values(1, '1'), (2,'2'), (3, '3') ....
所以,当rewriteBatchedStatements=true时, 楼主的例子会被编译为以上形式,当然values里全是?, mysql 客户端会对这些值添加参数. 这样的方式当然就快很多了。
其实到现在还不太了解 batch 处理时,执行计划这个概念,不过我猜 mysql 可能并没有缓存执行计划。而只是将这些语句组合起来了。
所以如果是这样,他的机制与oracle可能是有所不同的,还不是达到最高效的机制,也许这就是开源与商业的区别吧。
我们如果想更深入了解,只能借助于一些服务器端监视工具,sql分析工具了。
写贴子过程断断续续给打扰了,本来还有一些可以写更详细的,就留给大家自己去探索了,包括,如果调用addBatch(String sql)后,则仍会按照 executeBatchSerially 方式执行,包括何时执行 executePreparedBatchAsMultiStatement,都可以继续深入了解。
后记,当使用 update 时,会执行 executePreparedBatchAsMultiStatement,但是如果攒的语句太多,会导致 mysql 崩溃. 我的测试中10000条update不会有事,20000时,mysql 就崩掉了。
针对rewriteBatchedStatements=true 参数我做了测试,我加了这个参数,做同们的插入10万条记录测试:
我的mysql 安装的虚拟机上,所以慢一些。
MySql JDBC 驱动版本 | 结果 |
5.0.8 | 没有提高 18秒 |
5.1.7 | 没有提高 18秒 |
5.1.13 | 有提高 1.6秒 |
看来加了rewriteBatchedStatements=true 参数,还要保证mysql JDBC驱的版本。
http://dev.mysql.com/downloads/connector/j 这是官方的下载地址,目前最新版本是5.1.13
最后感谢srdrm ,这位朋友太能钻研了,呵呵。
1、个人的结论和看法都是片面的过时的
2、问题是越辩越清的
3、技术的争议是有益的
4、mysql是在进步的
5、大家都是好样的
先说结果:
按楼主的代码原封不动,在我的机器上执行情况是 8000 毫秒左右,
优化后,非batch 方式保持不变,batch 的方式 1000毫秒
楼主的测试结果让人非常失望...
但mysql名气这么大,不太相信在这样的问题上会出现与oracle这么大的差距。
直觉告诉我,应该是一些简单的设置问题,事实上最后得到的结果也是如此的。
带着解决这个疑惑的想法,依据大家之前得到的一些结果,信息,开始测试
工具:
eclipse-3.6, mysql-5.1.48, mysql-jdbc-driver 5.1.11, mysql workbench
前面说过了,我直觉认为代码不会有问题,所以先着手改善mysql 的服务器配置,innodb的设置。
改了几个参数,都没有什么效果。加大了日志缓存,只是提高到7000多毫秒。最后甚至很多歪门邪道的设置都大胆用了,一度让mysql 无法启动。。。最终都收效甚微,这个步骤大概试了将近一个小时。
这条路看来是走不通了。。得寻找别的方法
冷静下来想想,其实从代码中应该是可以发现些端倪
楼主的非batch代码中,每次调用 execute() 其实是会通过网络发送一条语句到服务器端的,是不会在客户端排队攒着的。
因为这个方法必须返回一个结果。它必然跟服务器发生了一次交互。
而在batch处理的代码中,其addBatch 就是无返回值,它提供了一个可能就是在客户端将语句缓存排队攒着,最后executeBatch时才发送到服务器端。
用代码可以证明,在batch处理方法的代码中,在 executeBatch, 及 commit 方法执行前,分别安插两条打印时间语句:
System.out.println("before executeBatch. "+ (System.currentTimeMillis()-a)+" ms"); prest.executeBatch(); System.out.println("before commit. "+ (System.currentTimeMillis()-a)+" ms"); conn.commit();
在我机器上的结果是,
before executeBatch. 279 ms before commit. 7922 ms MySql批量插入10万条记录用时7923 ms
说明客户端在攒语句时,相当的快,279毫秒就完成了,但在 executeBatch 这个方法的调用过程中,花费了 7920 减 去 279 的毫秒数。大部分都耗在这里了。 最后提交事务非常快,1毫秒而已
想想看,前边说过,非batch和batch的处理几乎是一样的时间。
可不可以先假设 batch 的方式与非batch一样,每一条insrt语句事实上均是单独发往服务器的呢?
浏览下源代码吧。
好几位兄弟都描述了源代码,直接从那几个类入手吧,事实上关键的类是这个 com.mysql.jdbc.PreparedStatement
先看了其中的 addBatch 方法,没有任何问题,只是将语句添加进入一个 List 中保存。
那么 executeBatch 呢?
再贴一下吧, 关键看其中的这部分,顺带说一下, 这个mysql-jdbcdriver的源代码是 5.1.13的
try { clearWarnings(); if (!this.batchHasPlainStatements && this.connection.getRewriteBatchedStatements()) { if (canRewriteAsMultiValueInsertAtSqlLevel()) { return executeBatchedInserts(batchTimeout); //执行路径之一 } if (this.connection.versionMeetsMinimum(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(); }
其实最终,executeBatch 的执行路径有三种可能。代码中我已标出来
不小心按了提交了,继续编辑此回复吧。
代码不算太复杂,但是有一个参数能帮助我们更快的确定mysql的batch工作机制,那就是
mysql jdbc driver 的connection url, 其中有一个参数是: rewriteBatchedStatements
完整的参数参考看这里:http://ftp.ntu.edu.tw/ftp/pub/MySQL/doc/refman/5.1/en/connector-j-reference-configuration-properties.html
rewriteBatchedStatements 参数默认为false, 需要手工设置为true,设置方式大概像这样:
String connectionUrl="jdbc:mysql://192.168.1.100:3306/test?rewriteBatchedStatements=true";
默认时候,rewriteBatchedStatements=false时,执行路径会跳到 executeBatchSerially,此方法内部将语句一条条发送,与非batch处理简直一样,所以慢,就在这里了。
当设为 true时,会执行executeBatchedInserts方法,事实上mysql支持这样的插入语句
insert into t_user(id,uname) values(1, '1'), (2,'2'), (3, '3') ....
所以,当rewriteBatchedStatements=true时, 楼主的例子会被编译为以上形式,当然values里全是?, mysql 客户端会对这些值添加参数. 这样的方式当然就快很多了。
其实到现在还不太了解 batch 处理时,执行计划这个概念,不过我猜 mysql 可能并没有缓存执行计划。而只是将这些语句组合起来了。
所以如果是这样,他的机制与oracle可能是有所不同的,还不是达到最高效的机制,也许这就是开源与商业的区别吧。
我们如果想更深入了解,只能借助于一些服务器端监视工具,sql分析工具了。
写贴子过程断断续续给打扰了,本来还有一些可以写更详细的,就留给大家自己去探索了,包括,如果调用addBatch(String sql)后,则仍会按照 executeBatchSerially 方式执行,包括何时执行 executePreparedBatchAsMultiStatement,都可以继续深入了解。
后记,当使用 update 时,会执行 executePreparedBatchAsMultiStatement,但是如果攒的语句太多,会导致 mysql 崩溃. 我的测试中10000条update不会有事,20000时,mysql 就崩掉了。
针对rewriteBatchedStatements=true 参数我做了测试,我加了这个参数,做同们的插入10万条记录测试:
我的mysql 安装的虚拟机上,所以慢一些。
MySql JDBC 驱动版本 | 结果 |
5.0.8 | 没有提高 18秒 |
5.1.7 | 没有提高 18秒 |
5.1.13 | 有提高 1.6秒 |
看来加了rewriteBatchedStatements=true 参数,还要保证mysql JDBC驱的版本。
http://dev.mysql.com/downloads/connector/j 这是官方的下载地址,目前最新版本是5.1.13
最后感谢srdrm ,这位朋友太能钻研了,呵呵。
2、使用mysql的multi insert,前面有人说了,就不重复。
3、前面还有人说“插入前,去掉索引和其他约束,插入完成后,再加上”,个人觉得不尽然,主键索引是必须的,否则InnoDB默认会自动生成作为聚簇索引的主键,再建主键索引应该会导致对记录重新进行物理组织。另外,在建表时就建好索引是有好处的,1、中提的insert buffer不会马上更新索引的叶子页,而是把若干对同一页面的更新缓存起来,一次性更新。当然insert buffer只对辅助索引有效。
4、如果对表加锁,插入完解除应该也可以提高一点速度。
不成熟的经验,以上欢迎大家讨论。
楼主的测试结果让人非常失望...
但mysql名气这么大,不太相信在这样的问题上会出现与oracle这么大的差距。
直觉告诉我,应该是一些简单的设置问题,事实上最后得到的结果也是如此的。
带着解决这个疑惑的想法,依据大家之前得到的一些结果,信息,开始测试
工具:
eclipse-3.6, mysql-5.1.48, mysql-jdbc-driver 5.1.11, mysql workbench
前面说过了,我直觉认为代码不会有问题,所以先着手改善mysql 的服务器配置,innodb的设置。
改了几个参数,都没有什么效果。加大了日志缓存,只是提高到7000多毫秒。最后甚至很多歪门邪道的设置都大胆用了,一度让mysql 无法启动。。。最终都收效甚微,这个步骤大概试了将近一个小时。
这条路看来是走不通了。。得寻找别的方法
冷静下来想想,其实从代码中应该是可以发现些端倪
楼主的非batch代码中,每次调用 execute() 其实是会通过网络发送一条语句到服务器端的,是不会在客户端排队攒着的。
因为这个方法必须返回一个结果。它必然跟服务器发生了一次交互。
而在batch处理的代码中,其addBatch 就是无返回值,它提供了一个可能就是在客户端将语句缓存排队攒着,最后executeBatch时才发送到服务器端。
用代码可以证明,在batch处理方法的代码中,在 executeBatch, 及 commit 方法执行前,分别安插两条打印时间语句:
System.out.println("before executeBatch. "+ (System.currentTimeMillis()-a)+" ms"); prest.executeBatch(); System.out.println("before commit. "+ (System.currentTimeMillis()-a)+" ms"); conn.commit();
在我机器上的结果是,
before executeBatch. 279 ms before commit. 7922 ms MySql批量插入10万条记录用时7923 ms
说明客户端在攒语句时,相当的快,279毫秒就完成了,但在 executeBatch 这个方法的调用过程中,花费了 7920 减 去 279 的毫秒数。大部分都耗在这里了。 最后提交事务非常快,1毫秒而已
想想看,前边说过,非batch和batch的处理几乎是一样的时间。
可不可以先假设 batch 的方式与非batch一样,每一条insrt语句事实上均是单独发往服务器的呢?
浏览下源代码吧。
好几位兄弟都描述了源代码,直接从那几个类入手吧,事实上关键的类是这个 com.mysql.jdbc.PreparedStatement
先看了其中的 addBatch 方法,没有任何问题,只是将语句添加进入一个 List 中保存。
那么 executeBatch 呢?
再贴一下吧, 关键看其中的这部分,顺带说一下, 这个mysql-jdbcdriver的源代码是 5.1.13的
try { clearWarnings(); if (!this.batchHasPlainStatements && this.connection.getRewriteBatchedStatements()) { if (canRewriteAsMultiValueInsertAtSqlLevel()) { return executeBatchedInserts(batchTimeout); //执行路径之一 } if (this.connection.versionMeetsMinimum(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(); }
其实最终,executeBatch 的执行路径有三种可能。代码中我已标出来
不小心按了提交了,继续编辑此回复吧。
代码不算太复杂,但是有一个参数能帮助我们更快的确定mysql的batch工作机制,那就是
mysql jdbc driver 的connection url, 其中有一个参数是: rewriteBatchedStatements
完整的参数参考看这里:http://ftp.ntu.edu.tw/ftp/pub/MySQL/doc/refman/5.1/en/connector-j-reference-configuration-properties.html
rewriteBatchedStatements 参数默认为false, 需要手工设置为true,设置方式大概像这样:
String connectionUrl="jdbc:mysql://192.168.1.100:3306/test?rewriteBatchedStatements=true";
默认时候,rewriteBatchedStatements=false时,执行路径会跳到 executeBatchSerially,此方法内部将语句一条条发送,与非batch处理简直一样,所以慢,就在这里了。
当设为 true时,会执行executeBatchedInserts方法,事实上mysql支持这样的插入语句
insert into t_user(id,uname) values(1, '1'), (2,'2'), (3, '3') ....
所以,当rewriteBatchedStatements=true时, 楼主的例子会被编译为以上形式,当然values里全是?, mysql 客户端会对这些值添加参数. 这样的方式当然就快很多了。
其实到现在还不太了解 batch 处理时,执行计划这个概念,不过我猜 mysql 可能并没有缓存执行计划。而只是将这些语句组合起来了。
所以如果是这样,他的机制与oracle可能是有所不同的,还不是达到最高效的机制,也许这就是开源与商业的区别吧。
我们如果想更深入了解,只能借助于一些服务器端监视工具,sql分析工具了。
写贴子过程断断续续给打扰了,本来还有一些可以写更详细的,就留给大家自己去探索了,包括,如果调用addBatch(String sql)后,则仍会按照 executeBatchSerially 方式执行,包括何时执行 executePreparedBatchAsMultiStatement,都可以继续深入了解。
后记,当使用 update 时,会执行 executePreparedBatchAsMultiStatement,但是如果攒的语句太多,会导致 mysql 崩溃. 我的测试中10000条update不会有事,20000时,mysql 就崩掉了。
值得继续讨论下,不过这里仅讨论了PreparedStatement!
感觉可以在jdbc驱动实现原理类似:
传输批量文本数据+Load Data的preparedstatement
应该能大幅度的提高性能。
MySQL传输数据貌似是字符型的(字节数组),PreparedStatement 和Statement的语句传输时。在开头几个字节上面有区别。如果网络分析的话,可以反向解析。
感觉可以在jdbc驱动实现原理类似:
传输批量文本数据+Load Data的preparedstatement
应该能大幅度的提高性能。
还可以在 mysql 服务器端直接写存储过程批量执行,同时查看执行的计划,与java客户端访问比较看有什么差异。从而能更好的掌握java mysql jdbc.
另外,那个mysql jdbc connection parameters 确实很重要
看了一半猜是values接多条记录,果然是。。。
这个感觉降低了网络开销和事务数,但是还是每次解释sql。
还可以在 mysql 服务器端直接写存储过程批量执行,同时查看执行的计划,与java客户端访问比较看有什么差异。从而能更好的掌握java mysql jdbc.
另外,那个mysql jdbc connection parameters 确实很重要
发表评论
-
MySQL体系结构
2014-05-20 21:17 1885了解MySql必须牢牢记住其体系结构图,Mysql是由SQ ... -
mysql 性能优化方案
2014-05-18 00:28 2873内容简介:这是一篇关 ... -
从Oracle迁移到Mysql之前必须知道的50件事
2014-01-22 11:52 46241、 对子查询的优化表现不佳。 2、 对复杂查询的 ... -
mysql limit分页查询优化写法
2013-08-07 23:23 6065在mysql中进行分页查询 ... -
MySQL是否区分大小写
2013-08-04 10:28 30721、是否区分 库名、表名、列名、别名 的大小写? --- ... -
MySQL方面不错的文章
2013-08-03 21:13 1874MySQL方面不错的文章 以下文章源自博客:ht ... -
MySQL性能优化的最佳20+条经验
2013-08-02 14:37 2824今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于W ... -
Mac OS安装MySQL(使用二进制PGK包安装)
2013-07-26 23:03 15628下载二进制PGK安装 ... -
MySql分析整理命令
2013-05-19 13:16 2192Analyze Table 分析表----------- ... -
MySql优化参数论述
2013-05-04 10:10 2421ITEYE官方的MySQL优化论 ... -
关于 MySQL connections 的一些知识
2013-04-15 10:48 9412关于 MySQL connections 的一些知识 查 ... -
Got error 134,28 from storage engine
2013-04-12 15:54 5994Got error 134 from storage ... -
Mysql JDBC Url参数说明
2012-09-24 15:44 56677MySQL的 JDBC URL 格式 for Con ... -
MySql 插入(insert)性能测试
2012-06-02 22:48 26284MySql 插入(insert)性能测试 ... -
源码编译安装MySQL-5.1.58,常规参数优化配置
2009-11-13 14:07 46851. 软件获得 可以从官方网站获得稳定版本mysq ... -
源码编译安装MySQL-5.0.84
2009-08-15 10:43 4114安装mysql有三种方式: 1、RPM包安装 ... -
MySQL管理命令、备份与恢复、授权远程访问
2009-04-18 14:00 7489MySql命令行登录 ----------------- ... -
JDBC连接MySQL数据库
2009-02-25 10:59 8709JDBC连接MySql数据库 第一个例子: ...
相关推荐
在Java中,执行MySQL批量插入数据有多种方法,每种方法在性能上都有所不同。以下是对这些方法的详细分析: 方法1:单条插入 这是最基础的插入方式,每次循环都创建一个新的SQL语句并执行。这种方法的效率最低,因为...
经查阅官方文档,笔者了解到如果需要批量操作则需要开启 rewriteBatchedStatements 参数,而 JDBC 的 5.1 版本驱动默认的是 false。于是笔者在数据库连接上添加属性 rewriteBatchedStatements=true,并再次测试,...
11. **连接池配置**:可以通过设置属性,如`useServerPrepStmts`和`rewriteBatchedStatements`来优化批量操作和预编译语句的性能。 12. **断线重连**:当数据库连接因网络问题或其他原因断开时,Connector/J具有...
最后,书中还提到了如何将批量更新视为插入操作,利用MySQL的“INSERT ON DUPLICATE KEY UPDATE”功能,这一功能在Connector/J 5.1.8版本中被重写成多值插入的形式。需要注意的是,在使用自增ID的情况下不适用此方法...
本文将探讨如何优化这一过程,以实现快速插入,并通过具体的Java多线程和MySQL批量插入策略来提升性能。 首先,我们需要理解传统的逐条插入方式在大数据量下效率极低,因为它涉及到频繁的数据库交互,这会消耗大量...
8. **性能优化**:MySQL Connector/J 5.1.25版本对性能进行了优化,例如使用`useServerPrepStmts=true`和`rewriteBatchedStatements=true`等参数,可以提高批量插入和预编译语句的效率。 9. **兼容性**:此版本的...
在Java开发中,当需要向MySQL数据库大量插入数据时,使用JDBC的批量处理功能可以显著提高效率。本文将详细讲解如何使用JDBC批量插入10万条数据,并探讨其背后的原理和优化策略。 批量处理在JDBC中主要通过Statement...
在Java的JDBC API中,批量处理是一种提升数据库操作性能的有效方式,特别是在处理大量数据时。本篇文章将详细讲解如何使用JDBC在MySQL数据库中进行快速批量插入数据。 首先,批量插入的基本步骤包括: 1. **建立...
针对MySQL数据库,JDBCUtil的实现可能包括了特定的优化策略,如使用`useServerPrepStmts=false`和`rewriteBatchedStatements=true`等连接属性,以提升批量操作的效率。同时,`mysql update语句`可能涉及到事务管理,...
3. JDBC URL参数`rewriteBatchedStatements=true`对批量插入性能的提升。 4. 使用PreparedStatement执行动态SQL和批量插入。 5. Java代码中生成随机字符串的方法。 6. 数据表设计和字段类型的选择。 7. 大量数据插入...