为啥 JDBC 不支持批量获取数据?

Posted

技术标签:

【中文标题】为啥 JDBC 不支持批量获取数据?【英文标题】:Why doesn't JDBC support bulk fetching of data?为什么 JDBC 不支持批量获取数据? 【发布时间】:2015-08-03 10:30:53 【问题描述】:

JDBC 长期以来一直支持使用addBatchexecuteBatch 进行批量更新。为什么不支持添加一堆准备好的语句并获取一组结果集作为响应?

例如,如果我想为单个视图加载客户详细信息、基本帐户详细信息、基本卡详细信息、基本贷款详细信息等,我更愿意创建一堆准备好的语句并将准备好的语句附加到 ArrayList并将它们作为批处理执行。然后我将遍历结果集并处理数据。希望可以节省几次网络往返(假设我的查询是高效的)。

查询示例:

SELECT custid, first, last, age FROM Customer where custid = ?
SELECT custid, acno, accountname, accounttype, status FROM Account where custid = ?
SELECT custid, cardno, cardname, cardtype, status FROM CreditCard where custid = ?
SELECT custid, loanno, principal, rate FROM Loan where custid = ?

我可以想象几个假设的原因,这可能是一个坏主意。但是,我不确定在现实世界中哪个最有可能是正确的。

反对批量获取的假设原因:

    存在一些基本的网络/数据库堆栈/内存相关问题 这可以防止在同一个上执行一堆选择查询 连接和结果集保持打开状态。 响应处理代码过于繁琐,因为调用级别和单个语句级别可能存在异常。而且,必须正确关闭几个语句。 在减少网络调用的数量方面没有显着的性能提升。查询执行是主要瓶颈,网络往返成本微不足道。 可能会滥用此类功能。像这样与其他查询批量处理的单个非性能查询可能会导致应用程序性能不佳。

我之所以问这个问题是因为我经常看到很多 Join 查询将父子关系合并到一个 SQL 查询中,只是为了在一个调用中完成加载。

但是,随着表数量的增加,查询变得复杂。此外,父表信息在每个子表的每一行中重复。因此,单连接结果集中存在大量数据冗余。

连接查询示例:

SELECT custid, first, last, age, acno, accountname, accounttype, a.status, cardno, cardname, cardtype, c.status, loanno, principal, rate
FROM Customer cc, Account a, CreditCard c, Loan l 
WHERE a.custid=CC.custid(+) and c.custid=CC.custid(+) and l.custid=CC.custid(+)

【问题讨论】:

完全 不相关,但是:您应该真正习惯于使用显式 JOIN 运算符而不是 where 子句中的隐式连接 -特别是对于外部连接。这也是Oracle recommends:“Oracle 建议您使用 FROM 子句 OUTER JOIN 语法而不是 Oracle 连接运算符 【参考方案1】:

JDBC API确实支持这一点。

Statement.getMoreResults()可以告诉你通过execute()执行的SQL语句是否产生了多个ResultSet

getMoreResults() 的 JavaDocs 引用:

移动到此 Statement 对象的下一个结果,如果它是 ResultSet 对象,则返回 true,并隐式关闭使用 getResultSet 方法获得的任何当前 ResultSet 对象。

当下列情况成立时,没有更多结果:

// stmt is a Statement object<br>
((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))

但它取决于后端 DBMS 和 JDBC 驱动程序如果你可以使用它。一些 JDBC 驱动程序简单地拒绝使用单个 execute() 调用运行多个语句(主要作为防止 SQL 注入的一种手段),而其他驱动程序则不会。

所以在例如Postgres 你可以这样做:

boolean hasResult = stmt.execute(
  "select * from table_1;\n" +
  "select * from table_2;");

while (hasResult) 

  rs = stmt.getResultSet();
  while (rs.next()) 
  
    // process the result set
  
  hasResult = stmt.getMoreResults();

这甚至允许混合SELECT 和例如UPDATE 语句,如果您还检查 getUpdateCount()

据我所知,您也可以使用 SQL Server 执行此操作。它不适用于 Oracle。

不过,我还没有用PreparedStatement 尝试过这个。但由于getMoreResults() 是为Statement 定义的,它也可用于PreparedStatement

【讨论】:

数字...我与 Oracle 后端合作过很多次。谢谢。 这是很好的信息,但即使您可以根据Statement 执行多个语句,您通常也可能不应该 JDBC 标准不允许一次执行多条语句;它确实需要在字里行间进行一些阅读,因为它没有明确说明,它只是在执行的上下文中始终如一地谈论语句(单数)。【参考方案2】:

如何将查询放入一个过程,然后使用 CallableStatement 执行该过程?

CallableStatement 可以返回一个或多个 ResultSet 对象 结果集对象。使用处理多个 ResultSet 对象 从 Statement 继承的操作。

  try 
  
      CallableStatement stmt = con.prepareCall(/* call procedure */);       

      boolean results = stmt.execute();
      int rsCount = 0;    

      while (results) 
      
           ResultSet rs = stmt.getResultSet();

           while (rs.next()) 
           

           
           rs.close();    
        results = stmt.getMoreResults();
       
      stmt.close();
   
   catch (Exception e) 
      e.printStackTrace();
   

【讨论】:

谢谢.. 这很接近!我认为这就是你的意思herongyang.com/JDBC/… 虽然,我希望我可以使用一组语句对临时查询做同样的事情 讨论了获取多个结果***.com/q/9696572/1737819【参考方案3】:

关系数据库经过设计和优化,可通过 SQL 查询检索来自多个表的JOIN 数据的数据。执行(正确)JOINs 数据的单个查询可能总是比使用单独查询获取相同数据更有效。

当单个查询变得过于复杂时,应将其重构为 VIEW——如果需要,您可以从中查询,并连接来自其他 TABLEs 和 VIEWs 的数据。

鉴于上述情况,我认为不需要批量查询。

【讨论】:

是的……这是真的。我对大量数据重复感到担忧。这种数据重复是否会成为问题?例如,您是否遇到过这样的情况:如果父子比例超过 1:100,则触发单独的查询会变得更加节省内存?【参考方案4】:

我感觉你不明白什么是准备好的语句。

准备好的语句是您声明一次的对象,然后通过为其提供的不同参数一直重复使用它。

您不是说每次您希望再次执行准备好的语句时都要从头开始重新创建它吗?

假设您有四个循环。在执行你的循环之前,你这样做:

 PreparedStatement statement1, statement2, statement3,statement4;
 try 
        con.setAutoCommit(false);//only needed when also doing updates/inserts
        statement1 = con.prepareStatement("SELECT custid, first, last, age FROM  Customer where custid = ?");
        statement2 = con.prepareStatement("SELECT custid, acno, accountname, accounttype, status FROM Account where custid = ?");
        // etc....
        for (Map.Entry<String, Integer> e : customers.entrySet()) 
            statement1.setInt(1, e.getValue().intValue());
            ResultSet rs = statement1.executeQuery();
            // do what you need to do
            statement2.setInt(1, e.getValue().intValue());
            ResultSet rs2 = statement2.executeQuery();
            // do what you need to do
         
         con.commit();//only needed when also doing updates/inserts
     

   

无需重新创建准备好的语句。这就是为什么它被称为prepared 声明。您只需向它提供它需要查询的新值。

通过这种方式,您可以将其添加到列表中,以您想要的方式对其进行迭代等等。所有这些都得到了优化,因为数据库引擎会记住查询计划和它为它所做的优化。你对准备好的语句对象做什么取决于你。

如果您不断地重新创建对象,它也会这样做,因为它会记住查询,但是您可以节省一遍又一遍地创建新对象的开销以及随之而来的内存问题。

所以,如果没有更明确的问题,这是我能给你的最佳答案。

【讨论】:

你说得对,在我的例子中我不需要使用 PreparedStatement。我的问题甚至适用于普通的声明。我可以在一次网络调用中执行 5 条语句并获得一组 ResultSet 对象吗? 定义网络调用。您的应用程序有一个到数据库服务器的开放网络。通过这种连接,它不断地来回交谈。在幕后它将通过同一连接进行 5 次呼叫。但我相信即使是“批处理”对象也会做一些巫术,最后如果你监控连接,它将是单独的查询。因此,只需为它实现自己的包装器即可。如果你真的需要高效率,你需要走复杂的查询路线。如果您希望具有可维护性,请走 5 次调用路线,然后塑造成您想要的形状。我将添加一个示例。 我假设一个查询 = 一个网络往返(至少对于小型结果集)。也就是说,如果我触发 4 个单独的小查询,每个查询返回 1KB 的结果集,成本会很高。批量处理它们并一次性获得 4KB 的结果会更便宜(更快)。这是错的吗? 必须放弃添加示例,我没有时间 atm 这样做,因为您无法保留结果集本身,因为游标会在重新执行代码时更新。您需要将值等存储在自定义对象中,或者存储查询并进行自己的批处理过程。 ------ 4kb 会更快是的。但除非你有非常非常高的流量或非常大的数据量,否则它是可以忽略不计的。(在 1000mbit 连接上每秒超过 128000 个查询),我不会针对它进行优化。 从你所说的仅仅分组查询可能不会有很大的性能提升。我会用 postgres 做一些基准测试,看看结果如何。谢谢!

以上是关于为啥 JDBC 不支持批量获取数据?的主要内容,如果未能解决你的问题,请参考以下文章

JDBC批量处理

hibernate的update及JDBC数据库批量操作

hibernate的update及JDBC数据库批量操作

如何从 Oracle 中的 JDBC 批量插入中获取生成的密钥?

使用 Spring 的 JDBC 支持获取输出参数

JDBC — 学习大纲