聊聊 JDBC 的 executeBatch || 对比下不同数据库对 JDBC batch 的实现细节 || 剖析下 Mysql 的 参数 rewriteBatchedStatements || 剖析下 pg 的参数 reWriteBatchedInserts

大家好,我是明哥!

上篇博文,“对比下 datax 的 OceanBase/MYSQL 不同数据同步方案的效率差异 || 聊聊参数 rewriteBatchedStatements” 发表后,有小伙伴问到不同数据库对 JDBC 批量更新的实现细节,所以通过本片博文,我们系统看下 jdbc batch 相关知识。

1. JDBC batch 为什么能提高性能?

通过查看源码可知,JDBC1.2 引入了 Batch 功能,涉及的API主要有以下四个:

  • java.sql.Statement#addBatch
  • java.sql.PreparedStatement#addBatch
  • java.sql.Statement#executeBatch
  • java.sql.Statement#clearBatch

JDBC 引入上述 batch 功能的主要目的,是加快对客户端SQL的执行和响应速度,并进而提高数据库整体并发度,而 jdbc batch 能够提高对客户端SQL的执行和响应速度,其主要原理有:

  • 减少了JDBC客户端和数据库服务器之间网络传输的开销:使用 batch 功能前,每提交一个SQL,都需要一次网络IO开销,且提交后需要等待服务端返回结果后,才能提交下一个SQL;而使用 batch 功能后,客户端的多个SQL是一起提交给服务器的,只涉及到一次网络IO开销(single database round trip),其示意图如下:

  • 当batch底层使用的是静态SQL并参数化执行时(JAVA中一般是使用类java.sql.PreparedStatement 来参数化执行静态SQL),数据库服务器可以只做一次解析:利用对参数化机制的支持,数据库服务器仅需要对 PreparedStatement 做一次解析(sql parse),即可传入不同参数执行该 batch 中所有的 SQL;

  • 网上有个帖子,详细对比了不同场景下,不同数据库的插入和更新性能的差异,可以看出,ORACLE/PG/MYSQL 使用 batch 功能后,性能都有了3-5被的提高:

2. JDBC batch 的使用场景和限制有哪些?

  • batch 功能对 statement 和 PreparedStatement 都有效,但为了避免 SQL 注入的风险,不推荐使用动态SQL,而是推荐使用静态 SQL 和绑定变量(也就是使用 PreparedStatement 和 stored procedures);
  • 从上述JDBC的API源码可以看出,batch 功能对所有SQL 都有效, 包括 SELECT/INSERT/UPDATE/DELETE,但由于使用 batch 功能后,返回值是 int[] 数组,不太方便获取 batch 底层每个sql的执行结果,所以大家一般不会对 SELECT 语句使用 batch 功能 (毕竟select查询的目的是获得每个select语句的结果resultSet),而只会在大量 INSERT/UPDATE/DELETE 的场景下,尤其是大批量插入的场景下,使用 batch 功能,所以大家提到 batch时,常说“批量更新”;(数据仓库数据湖等涉及到数据集成和ETL的场景,经常会使用到批量插入);
  • 另外需要注意的是,使用 batch 功能并不代表所有的 SQL 都在一个事务里:在 autocommit 模式下,after each statement you have created, the database will ensure that the result persists correctly before moving on to the next statement,if the nth sentence of a batch raises a constraint exception, all previously inserted rows will not be rollbacked;

3. 不同数据库对 JDBC batch 的实现有何异同?

  • 由于 batch 功能是通过 JDBC API 定义的,是 JDBC 规范的一部分,所以所有实现了 JDBC 接口的数据库驱动,都需要实现这些接口,所以 mysql/oracle/pg/db2/sqlserver 等所有提供了 JDBC 接口的数据库,原则上都支持 jdbc batch 功能;
  • 但不同数据库对这些接口的具体实现是不同的,所以其最终效果和使用细节会有些差异,甚至相同数据库驱动的不同版本,也可能会有所差异,所以用户在使用前,需要查看下对应数据库的说明,不能想当然地认为,某个数据驱动的参数,也同样使用于其它数据库驱动;
  • 这里重点指出下,jdbc batch 相关参数,mysql 有参数 rewriteBatchedStatements,postgresql 有参数 reWriteBatchedInserts;

4. 详解 mysql 的参数 rewriteBatchedStatements

  • MySQL JDBC 驱动虽然实现了各个 JDBC batch api,但默认情况下,其对 batch 功能的支持仅仅是语法层面的支持,并没有真正通过 batch 功能提升性能:即使用户代码中编写了 executeBatch(), mysql 仍会把用户期望批量执⾏的⼀组sql语句拆散,逐条发给MySQL数据库,所以 mysql 的批量插入实际上是单条插入,性能较低;(By default, the MySQL Jdbc driver ignores the executeBatch () statement, disassembles a set of SQL statements that we expect to execute in batches, and sends them to the MySQL database one by one, which directly causes lower performance.)
  • 比如以下批量插入测试代码,虽然代码中使用了batch api ps.executeBatch(),但通过 wireshark 在代码执行时抓包查看,可以发现底是将批量执⾏的⼀组sql语句拆散并逐条发给数据库服务器的,也就是说 mysql 的批量插入实际上是单条插入:
  • 测试代码如下:
    /**     * mysql batch insert, without rewriteBatchedStatements=true in the url     */    public static void testMysqlBatch1(){        try{            Class<?> mysqlDriverClass = Class.forName("com.mysql.cj.jdbc.Driver");            Connection con=DriverManager.getConnection("jdbc:mysql://10.23.2.215:33061/hs_cic","root","hundsun");            String sql = "insert into test_liming (AIR_CODE, AIR_NAME) values (?,?)";            PreparedStatement ps = con.prepareStatement(sql);            for (int i = 0; i < 10; i++) {                ps.setString(1,Integer.toString(i));                ps.setString(2,Integer.toString(i));                ps.addBatch();            }            int[] results = ps.executeBatch();            for (int i = 0; i < results.length; i++) {                System.out.println("results:" + results[i]);            }            con.close();        }catch(Exception e){ System.out.println(e);}    }
  • Mysql 提供了其特有的 JDBC 连接参数 rewriteBatchedStatements,当把该参数置为 true 时, mysql jdbc 驱动会在客户端重写用户提交的原始 SQL,并将重写后的 SQL “send the batched statements in a single request”;
  • 比如以下批量插入测试代码,代码中使用了batch api ps.executeBatch(),且 url中指定了rewriteBatchedStatements=true, 通过 wireshark 在代码执行时抓包查看,可以发现底是将批量执⾏的⼀组 sql Insert 语句,改写为一条 batched 语句 insert into tableA (colA,colB) values (colA-value1,colB-value1),(colA-value2,colB-value2),(colA-value3,colB-value3), 并通过一次请求发送给数据库服务器的,也就是说此时 mysql 使用了批量插入功能;
  • 测试代码如下:
    /**     * mysql batch insert, with rewriteBatchedStatements=true in the url     */    public static void testMysqlBatch2(){        try{            Class<?> mysqlDriverClass = Class.forName("com.mysql.cj.jdbc.Driver");            Connection con=DriverManager.getConnection("jdbc:mysql://10.23.2.215:33061/hs_cic?rewriteBatchedStatements=true","root","hundsun");            String sql = "insert into test_liming (AIR_CODE, AIR_NAME) values (?,?)";            PreparedStatement ps = con.prepareStatement(sql);            for (int i = 0; i < 10; i++) {                ps.setString(1,Integer.toString(i));                ps.setString(2,Integer.toString(i));                ps.addBatch();            }            int[] results = ps.executeBatch();            for (int i = 0; i < results.length; i++) {                System.out.println("results:" + results[i]);            }            con.close();        }catch(Exception e){ System.out.println(e);}    }
  • 经笔者测试总结,对批量插入的ps.executeBatch(),mysql jdbc 驱动,会改写批量中的一组sql为一条 “multi-values” 语句,并一次性提交给数据库服务器:

    • batchInsert(10 records) 会被改写为 "insert into t (…) values (…), (…), (…)” 并一次性提交;
    • 如果不能被改写为 "multi-values", 则会改写为多个;分割的sql语句并一次性提交:语句 “INSERT INTO TABLE(col1) VALUES (?) ON DUPLICATE KEY UPDATE col2=?” 与变量 [1,2] 和 [2,3],会被改写为 “INSERT INTO TABLE(col1) VALUES (1) ON DUPLICATE KEY UPDATE col2=2;INSERT INTO TABLE(col1) VALUES (3) ON DUPLICATE KEY UPDATE col2=4” 并一次性提交;
  • 经笔者测试总结,对批量删除和批量更新的ps.executeBatch(),mysql jdbc 驱动,会改写SQL语句,改写为多条;分割的 SQL 语句,并一次性提交给数据库服务器:

    • batchDelete(10 records) 会被改写为 "delete from t where id = 1; delete from t where id = 2; delete from t where id = 3;…."并一次性提交;
    • batchUpdate(10 records) 会被改写为 “update t set… where id = 1; update t set… where id = 2; update t set… where id = 3…” 并一次性提交;
  • 有的小伙伴,可能会有疑问,为什么 MYSQL 不像别的数据库,比如 oracle 和 postgresql 那样,默认支持 batch 功能呢?为什么必须手动指定参数 rewriteBatchedStatements=true,才会支持通过改写SQL 支持batch 功能呢?笔者也不是很清楚,不过大概猜测如下:

    • 改写后的SQL语句,很多时候并不只是简单地通过符号”;”来分割和追加原始的多个SQL,这有时候并不是我们期望的;
    • 并不是所有的 SQL 语句都能被很好地改写;
    • 当部分语句的执行可能会出错时,错误处理不太方便,查看 update counts 也不太方便;
    • 可能还有其它笔者不知道的原因。

最后总结 rewriteBatchedStatements 如下:

  • MySQL JDBC driver defines the rewriteBatchedStatements connection property, so that statements get rewritten into a single String buffer;
  • Without setting this property, the MySQL driver simply executes each DML statement separately, therefore defeating the purpose of batching;
  • property rewriteBatchedStatements may be specified in either the connection URL or an additional Properties object parameter to DriverManager.getConnection;
  • In order to fetch the auto-generated row keys, the batch must contain insert statements only;
  • For PreparedStatement, this property rewrites the batched insert statements into a multi-value insert;
  • the driver is not able to use server-side prepared statements when enabling rewriting;

5. 抓个包看看,批量插入时 oracle 底层的 SQL 语句

作为跟 Mysql 的对比,我们测试下 oracle 中批量插入时,抓包获取的SQL语句。

  • oracle批量插入时,抓包内容如下,可以看到,底层使用了静态SQL和绑定变量:
  • oracle批量插入,底层使用了ps.executeBatch,测试代码如下:
 public static void testOracleBatch(){        try{            Class<?> driverManagerClass = Class.forName("java.sql.DriverManager");            Class<?> driverClass = Class.forName("java.sql.Driver");            Class<?> oracleDriverClass = Class.forName("oracle.jdbc.driver.OracleDriver");          Connection con=DriverManager.getConnection("jdbc:oracle:thin:@//10.20.155.75:1521/pdb19","hs_cic_cda","hundsun");            String sql = "insert into test_liming (AIR_CODE, AIR_NAME) values (?,?)";            PreparedStatement ps = con.prepareStatement(sql);            for (int i = 0; i < 10; i++) {                ps.setString(1,Integer.toString(i));                ps.setString(2,Integer.toString(i));                ps.addBatch();            }            int[] results = ps.executeBatch();            for (int i = 0; i < results.length; i++) {                System.out.println("results:" + results[i]);            }            con.close();        }catch(Exception e){ System.out.println(e);}    }

6. 介绍下 postgreSql 的参数 reWriteBatchedInserts

  • postgreSql 跟 oracle 一样,默认都是支持 jdbc batch 功能的(这点跟MYSQL不同!);
  • 但为了进一步优化性能,pg 在9.4.1208 版本后,又提供了参数 reWriteBatchedInserts,该参数默认值为 FALSE;
  • 当参数 reWriteBatchedInserts 为true时,pgjdbc 会将批量的 “insert into ... values(?, ?)” 改写为 “insert into ... values(?, ?), (?, ?)” ;
  • 这样的改写的好处是:减少了每个 statement 的开销;
  • 这样的改写的坏处是:如果某个语句执行失败的话, 整个 batch 都会失败; The default value is false.
  • 比如某次线上案列,查看pg日志,开启reWriteBatchedInserts前后日志如下:
未开启参数reWriteBatchedInserts的日志:LOG:  execute S_2: insert into post (title, id) values ($1, $2)DETAIL:  parameters: $1 = 'Post no. 1', $2 = '1'LOG:  execute S_2: insert into post (title, id) values ($1, $2)DETAIL:  parameters: $1 = 'Post no. 2', $2 = '2'开启参数reWriteBatchedInserts的日志:LOG:  execute <unnamed>: insert into post (title, id) values ($1, $2),($3, $4),($5, $6),($7, $8),($9, $10),($11, $12),($13, $14),($15, $16)DETAIL:  parameters: $1 = 'Post no. 1', $2 = '1', $3 = 'Post no. 2', $4 = '2', $5 = 'Post no. 3', $6 = '3', $7 = 'Post no. 4', $8 = '4', $9 = 'Post no. 5', $10 = '5', $11 = 'Post no. 6', $12 = '6', $13 = 'Post no. 7', $14 = '7', $15 = 'Post no. 8', $16 = '8'

参考链接:

  • https://leanpub.com/high-performance-java-persistence/read
  • http://java-persistence-performance.blogspot.com/2013/05/batch-writing-and-dynamic-vs.html
  • https://www.alibabacloud.com/blog/how-to-write-into-a-database-using-rewritebatchedinserts-parameter_597796

微信公众号主要用来信息的传播和分享,同名知识星球主要用来知识的沉淀和积累! 欢迎大家扫码加入免费知识星球 “明哥的IT随笔”,这是一个围绕泛大数据生态的技术交流社区,可以探讨任何IT技术话题和工作上的问题,一起学习共同进步!

image

聊聊 JDBC 的 executeBatch || 对比下不同数据库对 JDBC batch 的实现细节相关推荐

  1. idea如何给oracle添加数据_intelij idea下使用java和JDBC连接oracle数据库及简单的SQL操作...

    intelij idea下使用java和JDBC连接oracle数据库及简单的SQL操作 发布时间:2018-07-04 10:09, 浏览次数:2532 , 标签: intelij idea jav ...

  2. jdbc和mysql做游戏排行榜_MySQL数据库与JDBC编程

    欢迎关注公众号:xfxuezhang MySQL数据库与JDBC编程 JDBC (Java Database Connectivity) DDL(Data Definition Language,数据 ...

  3. mysql jdbc execute_MySQL JDBC Statement.executeBatch实践问题

    MySQL JDBC Statement.executeBatch实践(执行效率低) 现在很少使用原生jdbc去实现代码, 最近在测试MySQL批处理数据遇到一个问题: 执行Statement.exe ...

  4. Java数据库编程(JDBC)-入门笔记

    数据库(DB) 简介: • DB: Database = Data + Base • 数据库:数据+库,存放数据的库(场所) • 数据:规范.半规范.不规范数据 • 库 – 一个空间,一个场所 – 停 ...

  5. web前端,数据库,jdbc

    刘国斌 77331283   bjliugb@tedu.cn                 ----------------web-day01.html ###网站的架构 - CS:Client S ...

  6. Java小白修炼手册--第四阶段--JDBC(Java Database Connectivity : Java访问数据库的解决方案 )

    目录 JDBC原理 JDBC标准 JDBC是什么 使用JDBC优点 JDBC接 口及数据库厂商实现 ​JDBC工作原理 Driver ( 驱动程序)接口及驱动类加载 ​Connection( 连接,关 ...

  7. JAVA数据库编程(JDBC技术)-入门笔记

    本菜鸟才介入Java,我现在不急着去看那些基本的语法或者一些Java里面的版本的特征或者是一些晋级的知识,因为有一点.Net的OOP编程思想,所以对于Java的这些语法以及什么的在用到的时候在去发现学 ...

  8. Linux 下Oracle Client JAVA JDBC 集成点滴

    首先Java下根据JDBC规范连接数据库,有几种形式,参考 http://djdnmq.iteye.com/blog/356468 oracle 驱动oci thin 区别 Oracle客户端准备 1 ...

  9. oracle orm 实例 java_Oracle数据库的JDBC查询实例

    作为Java与数据库交互最古老的.最基础的规范,JDBC规范提供了访问底层数据库的接口,其他ORM框架都是在JDBC这块基石上构建的.下面我们看一个基本的JDBC查询例子: 首先在pom.xml中加入 ...

最新文章

  1. VMware Virtual SAN 互操作性:OpenStack
  2. Luogu P3975 [TJOI2015]弦论
  3. strlen() Bug
  4. C# unicode 编码 和 解码
  5. 【JAVA】接口中的default和static方法
  6. 为什么大公司都不用mfc和qt_百度竞价推广效果下降,为什么有的老板还是只愿意做百度推广?...
  7. 开源编译工具和编译软件
  8. CentOS 7 根目录分区扩容
  9. 优班图linux 命令,Linux 常用命令
  10. 两种前端在线json编辑器方案(无法解决number精度丢失问题)
  11. python读excel表格数据绘制图表_Python读取Excel数据并生成图表过程解析
  12. 数据库----如何将oracle语句转换成mysql语句
  13. DoG算子和LoG算子
  14. MySQL的登陆【数据库系统】
  15. 荐书 | 心理学如何编程,看看这9本书
  16. win服务器系统设置休眠时间,win7系统电脑设置休眠时间的操作方法
  17. 常微分方程各种类型方程表格汇总
  18. 阿里云MVP北京闭门会圆满落幕 多把“利剑”助力开发者破阵蜕变...
  19. Unveiling the java.lang.Out OfMemoryError
  20. GO connectex: A connection attempt failed because the connected party did not properly respond 已解决

热门文章

  1. 关于IP协议首部长度的计算
  2. python列表去空值_如何在Python列表中的列表中删除nan / null值? - python
  3. 万事不求人 三招必杀技清除IE顽固病毒(转)
  4. 运动耳机品牌排行榜,值得安利的几款运动耳机
  5. 远程管理Linux相关工具整理
  6. python 程序打包成exe py2exe
  7. vscode 对template 标签失效的问题
  8. Vscode 撤销快捷键 VS 返回撤销
  9. BT下载教程之UPnP功能使用、BT端口映射、内网外网之完全解析
  10. XScale交叉编译环境搭建及MPlayer移植