【问题标题】:JDBC batch operations understandingJDBC批处理操作理解
【发布时间】:2018-05-19 18:42:22
【问题描述】:

我在我的应用程序中使用 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<Query>();
          batchParameters = new ArrayList<ParameterList>();
        }

        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 邮件列表中提出的问题。

标签: java postgresql hibernate jdbc


【解决方案1】:

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

TL;DR:如果使用批处理 API,pgjdbc 确实使用较少的网络循环。 BatchedQuery 仅在 reWriteBatchedInserts=true 传递给 pgjdbc 连接设置时使用。

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

在查询执行方面,网络延迟通常是已用时间的重要组成部分。

假设情况是插入10行。

  1. 没有批处理(例如,只有 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 连接属性。该功能最初开发于https://github.com/pgjdbc/pgjdbc/pull/491

    使用 2 条语句插入 10 行是足够聪明的。第一个是 8 值语句,第二个是 2 值语句。使用 2 的幂可以使 pgjdbc 保持不同语句的数量正常,并且提高性能,因为经常使用的语句是服务器准备的(请参阅What's the life span of a PostgreSQL server-side prepared statement

    BatchedQuery 代表这种多值语句,因此您将看到该类仅用于reWriteBatchedInserts=true 情况。

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

【讨论】:

  • 感谢您的回答。我用这个标志测试了我的应用程序,批量插入性能提高了很多倍。但并非一切都像结果一样好。我看到在最新版本的 pg 驱动程序中,构造 do nothingdo update set column=value 是固定的。但是构造 on conflict do update设置列 =? 不起作用。可以理解,这不太可能导致单个 SQL 查询。因此我不得不禁用这个标志。
  • @Birthright,请随时为不起作用的案例创建问题(请参阅github.com/pgjdbc/pgjdbc/issues
  • @Birthright,这可能会解决您的问题:github.com/pgjdbc/pgjdbc/pull/1130
  • 谢谢,打开 reWriteBatchedInserts=true 后,我的速度提高了 5 倍
【解决方案2】:

批处理不会折叠或最小化完成的 SQL 语句数量;这一切都是为了优化 Hibernate 在其内存会话中缓存和刷新数据到数据库的方式。批处理和为您的操作找到合适的批处理大小的重要性在于在应用内存使用和数据库性能之间找到适当的平衡。

  • 如果在提交/刷新批处理之前执行过多查询,您将耗尽应用服务器内存
  • 如果批量太小并且提交/刷新过于频繁,您将无法获得最佳性能。

更多阅读在这里。

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

【讨论】:

    猜你喜欢
    • 2013-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多