文章目录

  • 一、前言
  • 二、问题
    • 1、url后面useServerPrepStmts是什么?
    • 2、url后面rewriteBatchedStatements是什么?
    • 3、这两个参数对语句执行有什么影响?
    • 4、这两个参数能带来多大的性能提升?
    • 5、psts.addBatch();如果把这一段注释掉,SQL就不会执行了,为什么?
  • 三、总结
  • 四、疑问
  • 五、参考

一、前言

    为了验证不同SQL在大数据量下的执行性能,需要往数据库批量插入几十万条数据。因为这是一个很普遍的需求,所以网上应该会有现成的代码。在一番搜索后,找到了下面的代码,这段代码实现了插入10万条数据只需2秒钟的功能。

package com.wave.checkin.wavecheckin.utils;import java.io.BufferedReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;public class MysqlBatchUtil {private String sql = "INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES (?,?,?,?)";private String charset = "utf-8";private String connectStr = "jdbc:mysql://localhost:3306/test";private String username = "root";private String password = "";private void doStore() throws ClassNotFoundException, SQLException, IOException {Class.forName("com.mysql.jdbc.Driver");//此处是测试高效批次插入,去掉之后执行时普通批次插入connectStr += "?useUnicode=true&characterEncoding=utf8&useServerPrepStmts=true&rewriteBatchedStatements=true&serverTimezone=GMT";Connection conn = (Connection) DriverManager.getConnection(connectStr, username, password);// 设置手动提交 conn.setAutoCommit(false); int count = 0;PreparedStatement psts = conn.prepareStatement(sql);String line = null;Date begin = new Date();long time = System.currentTimeMillis();for (int i = 0; i <= 100000; i++) {psts.setString(1, i + "var");psts.setInt(2, i);psts.setDate(3, new java.sql.Date(time));psts.setString(4, "1");// 加入批量处理 psts.addBatch();  count++;}// 执行批量处理 psts.executeBatch();// 提交 conn.commit(); Date end = new Date();System.out.println("数量=" + count);System.out.println("运行时间=" + (end.getTime() - begin.getTime()));conn.close();}public static void main(String[] args) {try {new MysqlBatchUtil().doStore();} catch (Exception e) {e.printStackTrace();}}
}

    虽说功能已经实现,但是原理还是得弄明白。

二、问题

    为了探究代码背后的运行逻辑,我提出了几个问题:

1、url后面useServerPrepStmts是什么?

    mysql官方文档搜索useServerPrepStmts,找到了下面一段话:

Two variants of prepared statements are implemented by Connector/J, the client-side and the server-side prepared statements. Client-side prepared statements are used by default because early MySQL versions did not support the prepared statement feature or had problems with its implementation. Server-side prepared statements and binary-encoded result sets are used when the server supports them. To enable usage of server-side prepared statements, set useServerPrepStmts=true.

    大意就是 Connector/J(也就是JBDC)预编译分为客户端预编译和服务端预编译,默认是使用客户端预编译,因为早期版本MYSQL不支持预编译或者这功能有问题。如果要使用服务器预编译,就设置useServerPrepStmts=true.。
    那么,什么是服务器预编译呢。继续查。

MySQL 8.0 provides support for server-side prepared statements. This support takes advantage of the efficient client/server binary protocol. Using prepared statements with placeholders for parameter values has the following benefits:
    1. Less overhead for parsing the statement each time it is executed. Typically, database applications process large volumes of almost-identical statements, with only changes to literal or variable values in clauses such as WHERE for queries and deletes, SET for updates, and VALUES for inserts.
    2. Protection against SQL injection attacks. The parameter values can contain unescaped SQL quote and delimiter characters.

    大意就是MYSQL8.0支持服务端预编译语句。这类语句将参数用占位符替代,这样做的好处有以下两点:

  1. 减少每次语句执行时的语法解析。举个例子,select * from user where state = ?,这是一个预编译语句,只会解析一次,之后无论?传入什么参数都不需要再进行语法解析,达到“一次编译、多次运行"的效果;对于普通语句,只要SQL不是百分百一样,都需要进行语法解析。
  2. 防止SQL注入攻击。 参数值可以包含未转义的SQL引号和分隔符。

    另外补充一点,MySQL Server 4.1之前的版本是不支持预编译的,而Connector/J(也就是JBDC)在5.0.5以后的版本,默认是没有开启服务端预编译功能的。

2、url后面rewriteBatchedStatements是什么?

    mysql官方文档搜索rewriteBatchedStatements,找到了下面一段话:

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. Please be aware using rewriteBatchedStatements=true with INSERT … ON DUPLICATE KEY UPDATE that for rewritten statement server returns only one value as sum of all affected (or found) rows in batch and it isn’t possible to map it correctly to initial statements; in this case driver returns 0 as a result of each batch statement if total count was 0, and the Statement.SUCCESS_NO_INFO as a result of each batch statement if total count was > 0.

    翻译过来就是,在executeBatch()方法被执行时,是否使用多查询(无论是否设置allowMultiQueries属性)以及将用于插入的预编译语句重写为多值插入?请注意,如果使用java.sql.Statements并且没有对输入进行校验,那么就这有可能遭到SQL注入攻击。请注意,服务器预编译语句当前无法利用此重写选项,并且如果在使用PreparedStatement.set * Stream()时未指定流长度,则驱动程序将无法确定最佳的每批参数数量,您可能会从驱动程序收到错误消息,提示结果包太大。这些重写语句的Statement.getGeneratedKeys()仅在整个批处理都包含INSERT语句时才起作用。请注意,在INSERT上使用rewriteBatchedStatements = true。
    从这段话我们可以了解到,rewriteBatchedStatements对服务端无效,所以是作用于客户端的。当这个参数设置为true,在executeBatch()方法被执行时,预编译语句会重写成多值插入再传给服务端,而不是一条条SQL传给服务端。

3、这两个参数对语句执行有什么影响?

    现在知道了rewriteBatchedStatements用于重写客户端预编译语句,useServerPrepStmts用于开启服务端预编译功能。那么,现在就开始验证吧。笔者选用mysql5.7(已支持预编译)+Connector/J 8.0.18(默认未开启服务端预编译)。
    第一种情况,不加上rewriteBatchedStatements和useServerPrepStmts,循环插入两条数据,通过mysql通用日志查看语句执行情况。

?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
2019-11-01T01:43:51.832520Z     79 Query SHOW WARNINGS
2019-11-01T01:43:51.836262Z    79 Query SET NAMES utf8mb4
2019-11-01T01:43:51.836420Z    79 Query SET character_set_results = NULL
2019-11-01T01:43:51.836653Z    79 Query SET autocommit=1
2019-11-01T01:43:51.841252Z    79 Query SET autocommit=0
2019-11-01T01:43:51.865821Z    79 Query SELECT @@session.transaction_read_only
2019-11-01T01:43:51.866303Z    79 Query INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES ('0var',0,'2019-11-01','1')
2019-11-01T01:43:51.891904Z    79 Query INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES ('1var',1,'2019-11-01','1')
2019-11-01T01:43:51.892136Z    79 Query commit
2019-11-01T01:43:51.977174Z    79 Query rollback
2019-11-01T01:43:51.981484Z    79 Quit

    可以看出,服务端没有进行预编译,使用Query命令执行了两条新增SQL语句。
    第二种情况,设置useServerPrepStmts=true。

?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&useServerPrepStmts=true
2019-11-01T02:23:46.145078Z     89 Query SHOW WARNINGS
2019-11-01T02:23:46.148454Z    89 Query SET NAMES utf8mb4
2019-11-01T02:23:46.148588Z    89 Query SET character_set_results = NULL
2019-11-01T02:23:46.148809Z    89 Query SET autocommit=1
2019-11-01T02:23:46.153224Z    89 Query SET autocommit=0
2019-11-01T02:23:46.166311Z    89 Prepare   INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES (?,?,?,?)
2019-11-01T02:23:46.171129Z    89 Query SELECT @@session.transaction_read_only
2019-11-01T02:23:46.171392Z    89 Query SELECT @@session.transaction_read_only
2019-11-01T02:23:46.175887Z    89 Execute   INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES ('0var',0,'2019-11-01','1')
2019-11-01T02:23:46.204478Z    89 Execute   INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES ('1var',1,'2019-11-01','1')
2019-11-01T02:23:46.204878Z    89 Query commit
2019-11-01T02:23:46.487619Z    89 Query rollback
2019-11-01T02:23:46.492423Z    89 Quit

    可以看出,服务端进行预编译Prepare,使用Execute命令执行了两条新增SQL语句。
    第三种情况,设置rewriteBatchedStatements=true。

?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&rewriteBatchedStatements=true
2019-11-01T02:40:19.644778Z     90 Query SHOW WARNINGS
2019-11-01T02:40:19.650422Z    90 Query SET NAMES utf8mb4
2019-11-01T02:40:19.650650Z    90 Query SET character_set_results = NULL
2019-11-01T02:40:19.650932Z    90 Query SET autocommit=1
2019-11-01T02:40:19.655743Z    90 Query SET autocommit=0
2019-11-01T02:40:19.680848Z    90 Query SELECT @@session.transaction_read_only
2019-11-01T02:40:19.681998Z    90 Query INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES ('0var',0,'2019-11-01','1'),('1var',1,'2019-11-01','1')
2019-11-01T02:40:19.700368Z    90 Query commit
2019-11-01T02:40:19.725935Z    90 Query rollback
2019-11-01T02:40:19.730085Z    90 Quit

    可以看出,服务端进行没有进行预编译,使用Query命令执行了一条多值的新增SQL语句。
    第四种情况,同时设置rewriteBatchedStatements=true和useServerPrepStmts=true。

?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&rewriteBatchedStatements=true&useServerPrepStmts=true
2019-11-01T02:42:25.799594Z     93 Query SHOW WARNINGS
2019-11-01T02:42:25.803008Z    93 Query SET NAMES utf8mb4
2019-11-01T02:42:25.803138Z    93 Query SET character_set_results = NULL
2019-11-01T02:42:25.803354Z    93 Query SET autocommit=1
2019-11-01T02:42:25.807759Z    93 Query SET autocommit=0
2019-11-01T02:42:25.822828Z    93 Prepare   INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES (?,?,?,?)
2019-11-01T02:42:25.826277Z    93 Query SELECT @@session.transaction_read_only
2019-11-01T02:42:25.827500Z    93 Prepare   INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES (?,?,?,?),(?,?,?,?)
2019-11-01T02:42:25.832597Z    93 Execute   INSERT INTO query_data (`var_data`, `int_data`, `create_date`, `char_data`) VALUES ('0var',0,'2019-11-01','1'),('1var',1,'2019-11-01','1')
2019-11-01T02:42:25.851448Z    93 Close stmt
2019-11-01T02:42:25.851605Z    93 Query commit
2019-11-01T02:42:25.880361Z    93 Query rollback
2019-11-01T02:42:25.884408Z    93 Quit

    可以看出,服务端进行进行了两次预编译Prepare,使用Execute命令执行了一条多值SQL语句。

4、这两个参数能带来多大的性能提升?

    分别对上面提到的四种情况做测试,每一种情况下插入10万条数据,执行5次,记录时间,结果如下:

第一种情况,不设置rewriteBatchedStatements和useServerPrepStmts。
10934   8849    8195    11846   9388
第二种情况,设置useServerPrepStmts=true。
9837    7045    7553    7195    7931
第三种情况,设置rewriteBatchedStatements=true。
2799    1481    1456    1180    1543
第四种情况,同时设置rewriteBatchedStatements=true和useServerPrepStmts=true。
4211    1974    1867    2322    2381

    不难看出,只设置rewriteBatchedStatements=true带来的性能提升是很可观的,而只设置useServerPrepStmts=true只带来了一点点的提升。很有意思的一点是,同时设置rewriteBatchedStatements=true和useServerPrepStmts=true比只设置rewriteBatchedStatements=true的性能要略差一点。
    结合mysql通用日志进行分析,可以得到以下结论:
    1) 批量插入大量数据时设置rewriteBatchedStatements=true可以重写SQL语句,将多条SQL合并成一条SQL,再交由服务端处理,从而大大减少执行时间。
    2) 只设置useServerPrepStmts=true,可以略微提升性能,是因为Prepare-Execute的执行模式要比单一的Query更快。
    3) 为什么同时设置rewriteBatchedStatements=true和useServerPrepStmts=true比只设置rewriteBatchedStatements=true的性能要略差一点,是因为Prepare本身是有开销的,在只需要执行一条SQL的时候,这种开销相对来说会比较大。

5、psts.addBatch();如果把这一段注释掉,SQL就不会执行了,为什么?
 for (int i = 0; i <= 100000; i++) {psts.setString(1, i + "var");psts.setInt(2, i);psts.setDate(3, new java.sql.Date(time));psts.setString(4, "1");// 加入批量处理 psts.addBatch();  count++;}public void addBatch() throws SQLException {try {synchronized(this.checkClosed().getConnectionMutex()) {QueryBindings<?> queryBindings = ((PreparedQuery)this.query).getQueryBindings();queryBindings.checkAllParametersSet();this.query.addBatch(queryBindings.clone()); //}} catch (CJException var6) {throw SQLExceptionsMapping.translateException(var6, this.getExceptionInterceptor());}}AbstractQuery类:public void addBatch(Object batch) {if (this.batchedArgs == null) {this.batchedArgs = new ArrayList();}this.batchedArgs.add(batch); //this.batchedArgs为protect List<Object> batchedArgs;}

    总的来说,psts.addBatch(); 所做的就是把每一条新增SQL的参数给存到批量参数列表中,在调用psts.executeBatch方法时,将批量参数列表发送给服务端。

三、总结

    之所以插入10万数据只需2秒,主要原因是URL设置了rewriteBatchedStatements=true。

四、疑问

    在做性能测试的时候,每一种情况的第一次运行都会比后面几次要慢,但是MYSQL通用日志是一模一样的,这就很奇怪了。有时间的话再另外写一篇博客去探究下。

五、参考

MYSQL官方文档

为何插入10万数据只需2秒相关推荐

  1. GitHub标星近10万:只需5秒音源就能实时“克隆”你的声音!

    作者 | Google团队 译者 | 凯隐 编辑 | Jane 出品 | AI科技大本营(ID:rgznai100) 本文中,Google 团队提出了一种文本语音合成(text to speech)神 ...

  2. GitHub标星近1万:只需5秒音源,这个网络就能实时“克隆”你的声音

    作者 | Google团队 译者 | 凯隐 编辑 | Jane 出品 | AI科技大本营(ID:rgznai100) 本文中,Google 团队提出了一种文本语音合成(text to speech)神 ...

  3. mysql导入100000000需要多久_MYSQL批量插入千万级数据只需百秒

    1.首先建立一张student表函数 create table(id int(20) NOT NULL AUTO_INCREMENT,sex char(1),name varchar(20));spa ...

  4. 企业用好大数据只需这6招

    企业用好大数据只需这6招 大数据并不是我们说的数据大就是大数据,这种理解没事实际意义,大数据的核心并不在规模大,而是它蕴含的是计算和思维方式的转变.大数据的"大"是宏观多变的意思, ...

  5. php 导出excel分段导出_php 导出excel 10万数据

    php导出excel 10万数据(此代码主要测试用,没写单元测试 还在修改中 后期加上单元测试) 在工作当中要对一些基本信息和其他信息导出 起初信息比较小无所谓.... 但当信息超出65535的时候 ...

  6. 要算15万年难题只需1秒 量子计算机开启中国速度

    日前,中国科学技术大学潘建伟教授及其同事陆朝阳教授.朱晓波教授等,联合浙江大学王浩华教授研究组,在基于光子和超导体系的量子计算机研究方面取得了系列突破性进展.5月3日,该研究团队正式发布了这一系列研究 ...

  7. 不可思议!英伟达新技术训练NeRF模型最快只需5秒,代码已开源

    英伟达将训练 NeRF 模型从 5 小时缩至 5 秒. 你曾想过在 5 秒内训练完成狐狸的 NeRF 模型吗?现在英伟达做到了! 令人不可思议的是,就如谷歌科学家 Jon Barron 在推特上表示的 ...

  8. 合并excel文件 C语言,再见Ctrl + C!合并100个Excel表格,只需30秒!

    原标题:再见Ctrl + C!合并100个Excel表格,只需30秒! 哈喽,大家好!在上篇文章< 你复制粘贴的那么认真,难怪天天加班[Excel教程] >中,我们给大家介绍了4种拆分工作 ...

  9. 应用悄悄拿走你的隐私做了什么?只需30秒这个AI给你答案

    安妮 编译自 Futurism 量子位 出品 | 公众号 QbitAI "我已经阅读并了解--"可能是当代青年定期撒下的一个谎. 可能你深有体会. 几乎每个下载的App都有用户协议 ...

最新文章

  1. 数据结构与算法——线性结构——线性表及其表示
  2. 声卡硬件测试软件,RMAA声卡检测(RightMark Audio Analyzer)
  3. spark 2.2 读取 Hadoop3.0 数据异常 org.apache.hadoop.hdfs.web.HftpFileSystem cannot access its superinterfa
  4. 7-4 求下一天 (30 分)
  5. java声明时间为什么类型_JAVA--类的声明周期
  6. unix/mac/dos-windows三种文本文件的格式的行尾区别
  7. bat调用bat注意事项(不带上参数 /b 时 会直接退出)
  8. 函数拾取-python
  9. 图解25匹马的选马问题
  10. mysql好玩的代码_搞一些好玩的东西redis
  11. 460.LFU 缓存
  12. WPF界面-手机QQ_DEMO
  13. Notice: Undefined index: 提示解决方法
  14. 五年后的深圳是天堂还是地狱?
  15. Jenkins-API
  16. 开发利器之Mac下的MacPorts
  17. 小Hi和小Ho的礼物
  18. 室内定位方案之蓝牙定位+IBeacon室内定位技术解决方案-新导智能
  19. javascript设计模式-原型模式(prototype pattern)
  20. c语言双边滤波算法,浅析bilateral filter双边滤波器的理解

热门文章

  1. the connection to the server was unsuccessful(file ///android_asset/www/index.html)
  2. Y05 - 999、Python - 风变编程
  3. 小重山 【南宋】 岳飞
  4. 不会“思维”只会“批判”,谨防网络舆论“怨妇化”
  5. unity NullReferenceException: Object reference UnityEditor.Graphs.Edge.WakeUp () (at D:/unity/
  6. java算法优化_Java学习笔记---Java简单的代码算法优化(例)
  7. 阿里云新购服务器磁盘disk挂载完整教程
  8. 敏感性、特异性、假阳性、假阴性(sensitivity and specificity)
  9. 为什么 K8s 在阿里能成功(转)
  10. 量子计算机技术难,量子计算机是什么工作原理运行的?现在制造还存在什么技术上的难...