JDBC批处理操作的理解

我在我的应用程序中使用Hibernate ORM和PostgreSQL,有时我使用批处理操作。 起初我不明白为什么在批处理大小为25的日志中,会生成25个查询,并且最初认为它无法正常工作。 但之后我查看了pg驱动程序的源代码,并在PgStatement类中找到以下行:

public int[] executeBatch() throws SQLException { this.checkClosed(); this.closeForNextExecution(); if (this.batchStatements != null && !this.batchStatements.isEmpty()) { this.transformQueriesAndParameters(); //confuses next line, because we have array of identical queries Query[] queries = (Query[])this.batchStatements.toArray(new Query[0]); ParameterList[] parameterLists = (ParameterList[])this.batchParameters.toArray(new ParameterList[0]); this.batchStatements.clear(); this.batchParameters.clear(); 

并在PgPreparedStatement类中

  public void addBatch() throws SQLException { checkClosed(); if (batchStatements == null) { batchStatements = new ArrayList(); batchParameters = new ArrayList(); } batchParameters.add(preparedParameters.copy()); Query query = preparedQuery.query; //confuses next line if (!(query instanceof BatchedQuery) || batchStatements.isEmpty()) { batchStatements.add(query); } } 

我注意到,如果批处理的大小为25,则会发送25个查询并附加参数。

数据库的日志确认了这一点,例如:

 2017-12-06 01:22:08.023 MSK [18402] postgres@buzzfactory СООБЩЕНИЕ: выполнение S_3: BEGIN 2017-12-06 01:22:08.024 MSK [18402] postgres@buzzfactory СООБЩЕНИЕ: выполнение S_4: select nextval ('tests_id_seq') 2017-12-06 01:22:08.041 MSK [18402] postgres@buzzfactory СООБЩЕНИЕ: выполнение S_2: insert into tests (name, id) values ($1, $2) 2017-12-06 01:22:08.041 MSK [18402] postgres@buzzfactory ПОДРОБНОСТИ: параметры: $1 = 'test', $2 = '1' 2017-12-06 01:22:08.041 MSK [18402] postgres@buzzfactory СООБЩЕНИЕ: выполнение S_2: insert into tests (name, id) values ($1, $2) 2017-12-06 01:22:08.041 MSK [18402] postgres@buzzfactory ПОДРОБНОСТИ: параметры: $1 = 'test', $2 = '2' ... x23 queries with parameters ... 2017-12-06 01:22:08.063 MSK [18402] postgres@buzzfactory СООБЩЕНИЕ: выполнение S_5: COMMIT 

但我认为必须使用25个参数的数组执行一个查询。 或者我不明白批量插入如何与预准备语句一起使用? 为什么要复制一次查询n次?

毕竟,我试图在这个地方调试我的查询

 if (!(query instanceof BatchedQuery) || batchStatements.isEmpty()) { 

并注意到我的查询总是SimpleQuery的实例而不是BatchedQuery。 也许这是问题的解决方案? 有关BatchedQuery的信息我找不到

可能涉及各种类型的批处理,我将介绍PostgreSQL JDBC驱动程序(pgjdbc)的一部分。

TL; DR:在使用批处理API的情况下,pgjdbc确实使用较少的网络绕组。 仅当将reWriteBatchedInserts=true传递给pgjdbc连接设置时才使用BatchedQuery。

您可能会发现https://www.slideshare.net/VladimirSitnikv/postgresql-and-jdbc-striving-for-high-performance相关(幻灯片44,…)

在查询执行方面,网络延迟通常是经过时间的重要部分。

假设案例是插入10行。

  1. 没有批处理(例如,只是PreparedStatement#execute在循环中PreparedStatement#execute )。 驱动程序将执行以下操作

     execute query sync <-- wait for the response from the DB execute query sync <-- wait for the response from the DB execute query sync <-- wait for the response from the DB ... 

    值得注意的时间将花在“等待数据库”上

  2. JDBC批处理API。 也就是PreparedStatement#addBatch()使驱动程序能够在单个网络往返中发送多个“查询执行”。 然而,当前的实现仍然会将大批量分成较小的批次以避免TCP死锁。

    行动会好得多:

     execute query ... execute query execute query execute query sync <-- wait for the response from the DB 
  3. 请注意,即使使用#addBatch ,也会出现“执行查询”命令的开销。 服务器需要花费大量时间来单独处理每条消息。

    减少查询数量的方法之一是使用多值插入。 例如:

     insert into tab(a,b,c) values (?,?,?), (?,?,?), ..., (?,?,?) 

    此PostgreSQL允许一次插入多行。 缺点是您没有详细的(每行)错误消息。 目前,Hibernate没有实现多值插入。

    但是,自9.4.1209(2016-07-15)起,pgjdbc可以动态地将常规批量插入重写为多值。

    要激活多值重写,需要添加reWriteBatchedInserts=true连接属性。 该function最初是在https://github.com/pgjdbc/pgjdbc/pull/491中开发的

    它足够聪明,可以使用2个语句来插入10行。 第一个是8值语句,第二个是2值语句。 使用2的幂使pgjdbc能够保持不同语句的数量,并且这可以提高性能,因为常用的语句是服务器准备的(请参阅PostgreSQL服务器端预处理语句的生命周期 )

    BatchedQuery表示这种多值语句,因此您将看到reWriteBatchedInserts=true使用的类reWriteBatchedInserts=true

    该function的缺点可能包括:较低的细节作为“批处理结果”。 例如,常规批处理为您提供“per statement rowcount”,但在多值情况下,您只需获得“语句已完成”状态。 最重要的是,动态重写器可能无法解析某些SQL语句(例如https://github.com/pgjdbc/pgjdbc/issues/1045 )。

批处理不会崩溃或最小化SQL语句的数量; 它是关于优化Hibernate如何在其内存中会话中缓存和刷新数据库的方式。 批处理和为您的操作找到合适的批量大小的重要性在于在使用的应用程序内存和数据库性能之间找到适当的平衡。

  • 如果在提交/刷新批处理之前执行了太多查询,则将耗尽app服务器内存
  • 如果您的批量太小而且您经常进行/冲洗,您将无法获得最佳性能。

更多阅读在这里。

https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/batch.html https://www.tutorialspoint.com/hibernate/hibernate_batch_processing.htm