【问题标题】:resultSet.next() : Does it fetch data from buffer OR from database?resultSet.next() :它是从缓冲区还是从数据库中获取数据?
【发布时间】:2017-07-11 17:38:47
【问题描述】:

我下面有这样的代码,

try (Connection connection = this.getDataSource().getConnection();
         PreparedStatement statement = connection.prepareStatement(sqlQuery);) {


        try {
            statement.setFetchSize(10000); // Set fetch size
            resultSet = statement.executeQuery();

            while (true) {
                resultSet.setFetchSize(10000);
                boolean more = resultSet.next();
                if (! more) {
                    break;
                }
                // populating an arraylist from the value from resultSet
            }
        }
        catch (Exception e) {
            LOGGER.error("Exception : "+e);
        }
    } catch (SQLException e) {
        LOGGER.error("Exception : "+e);
    }

我的理解如下,

statement fetch size 为 10000。执行 statement.executeQuery() 时,返回 ResultSet 游标。它将在内存中有 10000 行。 当调用 resultSet.next 时,它会从内存缓冲区中获取一行。 (每次调用一行)。当内存中没有更多行时,再次触发查询,并再次从数据库中获取 10000 行并存储在缓冲区中。这一直持续到没有要从 DB 中获取的行

所以如果我的理解是正确的,总共有 210000 行会有多少实际的数据库调用?是21吗? (210000 / 10000)

还有何时和谁调用数据库(当缓冲区中的行全部被读取时)以获取更多行(在我的情况下为 10000 行)并存储在缓冲区中。还有什么时候清空缓冲区?

如果我的理解有误,请纠正我。

我需要处理 Oracle 数据库中的数百万数据。

感谢任何指针/信息

问候,

标清

【问题讨论】:

  • setFetchSize# 方法应该是一个提示。使用或忽略它完全取决于 JDBC 驱动程序。 who calls the DB --> 驱动程序也会这样做。大部分来自SO Q/A
  • 对于 OCI 驱动程序,文档中有一节描述了每次 OCI 调用的网络往返次数。 JDBC 驱动程序没有以这种方式记录 - 它提供了更高级别的 API。但是您可以使用 OCI 文档作为主题的介绍。

标签: java database oracle jdbc resultset


【解决方案1】:

抱歉,您的理解有误。不存在“再次触发查询”之类的东西。

查询执行一次。这将花费初始时间来处理查询(除了优化查询之外,您无法做任何事情),然后它将开始在服务器上生成行,这些行需要传输到客户端。在传输行时,服务器可能会继续生成更多要传输的行,并在服务器上缓冲它们。这种服务器端缓冲与我们在本问答中讨论的那种缓冲完全无关,而且您几乎无法控制它。 (也许通过服务器配置,如果有的话。)在某些时候,所有行都将在服务器上收集,然后唯一剩下要做的就是将剩余的行从服务器传输到客户端。

因此,据客户端所知,一旦将查询发送到服务器,在服务器考虑它时会有一定的延迟,之后行以通常一样快的速度变得可用因为电线可以承载它们。因此,客户端开始使用resultSet.next() 读取这些行。

在没有任何缓冲的情况下,每次调用resultSet.next() 都会从客户端向服务器发送一个请求,告诉它发送下一行,而服务器将只响应该行。这会很快产生第一行,但从长远来看效率会非常低,因为这会导致客户端和服务器之间的往返次数过多。

使用缓冲,对resultSet.next() 的第一次调用将向服务器请求一堆行。这将对接收第一行的时间施加惩罚,因为您将不得不等待通过线路发送 100 行,但从长远来看,它将显着减少总网络开销,因为只有每行在客户端和服务器之间进行一次往返。

resultSet.setFetchSize() 的理想策略是保持原样,不要太担心。

但是,如果您对性能有偏执,那么一个好的策略是从相当小的 fetch 大小(例如 10)开始,以便快速获取您的第一行,然后继续加倍直到达到某个特定值最大值(比如 100),超过这个值实际上没有任何改善。

【讨论】:

  • 您的最后一段似乎暗示我们可以在处理打开的 ResultSet 时更改其获取大小。你是这个意思吗?
  • @GordThompson 嗯,这就是我的意思。我只是假设它有效,因为当 OP 使用它时它没有失败。我当然可能是错的。我会假设它取决于 RDBMS 实现,但我没有看过标准。但有件事告诉我你的问题是修辞性的。你为什么不直接说你知道的,所以我可以修正最后一段?
  • @GordThompson 允许更改打开游标的提取大小。 JDBC 驱动程序是支持它还是忽略它,是实现定义的。
  • 谢谢,@Mark。我问是因为我真的很好奇;我以前从未见过这样做。
【解决方案2】:

只有 Oracle JDBC 驱动程序的作者才能回答您的问题。

也就是说,调用 db 来读取下一个数据块不会花费超过几毫秒(或更短)的时间,大部分时间将取决于传输速率,以及您如何从中获取数据结果集。

我认为,一旦您每次调用超过几百条记录,您就会陷入收益递减,设置更大的获取大小。

关于清除缓冲区,这主要是垃圾收集域,一旦你失去对结果集的引用。

只要确保您的语句是 FORWARD ONLY,无论是出于性能原因还是内存占用。

connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY );

【讨论】:

  • 有用链接:Oracle JDBC Documentation
  • 大于 1K 的提取大小可能不会产生太大影响。如果您想从数据库端查看发生了什么,您可以启用 SQL Tracing
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-27
  • 1970-01-01
  • 1970-01-01
  • 2019-05-04
  • 1970-01-01
相关资源
最近更新 更多