【问题标题】:How does a JDBC driver implement the setMaxRows methodJDBC驱动如何实现setMaxRows方法
【发布时间】:2015-12-22 23:23:54
【问题描述】:

根据 JDBC 规范,Statement.setMaxRows(int maxRows) 方法应该:

设置任何 ResultSet 的最大行数限制 此 Statement 对象生成的对象可以包含给定的 数字。如果超出限制,多余的行将被静默 掉了。

在针对限制 SQL 级别(ROWSET、TOP 和 LIMIT)的结果集进行测试时,JDBC 和 SQL 构造似乎都表现得非常好。

即使选择数百万行,setMaxRows 的性能似乎也没有变差。

会不会是因为数据库Executor使用的数据库游标只能按需获取记录,所以当驱动达到maxRows阈值时,可以指示数据库关闭游标?

这样,数据库不必选择一个庞大的结果集并将其发送到网络,而只是在客户端被丢弃。

【问题讨论】:

    标签: mysql sql-server oracle postgresql jdbc


    【解决方案1】:

    在 PostgreSQL 中,PgJDBC 在协议级别发送一个请求,相当于在查询中附加一个LIMIT。因此,数据库服务器知道尽可能减少它所做的工作量。例如,它可能会选择一个获取所有行的成本更高的计划,但它可以更快地开始返回一些行或避免大的全行排序。

    我希望其他引擎的客户端驱动程序类似 - 在幕后设置限制,或使用光标并读取,直到获得足够的结果。

    每个 DBMS 和驱动程序都会有所不同,因此可能很难找到一个明确的答案。

    【讨论】:

      【解决方案2】:

      大多数 JDBC 驱动程序将按需获取行(基于获取大小),因此通常maxRows 将非常有效。他们通常甚至优化为仅获取不超过maxRows

      ROWSTOP 可能会为数据库服务器提供一些优化查询的额外提示,因此设置maxRows 可能不如在查询本身中包含最大值那么有效。确切的行为取决于驱动程序和数据库,因此很难概括行为和性能特征。

      值得注意的例外是 MySQL 驱动程序(可能还有 MariaDB),它默认在查询执行时立即获取 所有 行(除非获取大小设置为 Integer.MIN_VALUE)。

      以 Jaybird(Firebird JDBC 驱动程序)为例,following 已完成(对于 TYPE_FORWARD_ONLY):

      public void fetch() throws SQLException {
          synchronized (syncProvider.getSynchronizationObject()) {
              checkClosed();
              int maxRows = 0;
      
              if (this.maxRows != 0) maxRows = this.maxRows - rowNum;
      
              int fetchSize = this.fetchSize;
              if (fetchSize == 0) fetchSize = MAX_FETCH_ROWS;
      
              if (maxRows != 0 && fetchSize > maxRows) fetchSize = maxRows;
      
              if (!allRowsFetched && (rows.isEmpty() || rows.size() == rowPosition)) {
                  rows.clear();
                  stmt.fetchRows(fetchSize);
                  rowPosition = 0;
              }
      
              if (rows.size() > rowPosition) {
                  setNextRow(rows.get(rowPosition));
                  // help the garbage collector
                  rows.set(rowPosition, null);
                  rowPosition++;
              } else {
                  setNextRow(null);
              }
          }
      }
      

      由于服务器可能决定发送比请求更多的行,因此会对 next() 进行额外检查。

      【讨论】:

      • 但是即使所有行都被获取,MySQL 不使用类似流的方法吗?在开始通过网络发送响应之前,它是否必须获取数据库内存中的所有行?
      • 默认 MySQL 行为是获取所有行并在客户端缓存它们,请参阅 ResultSet 下:dev.mysql.com/doc/connector-j/en/…。您可以使用流式结果集,但这有其他含义,因为在获取所有行之前,无法使用相同的连接来执行其他语句。我不知道 MySQL 协议如何工作的确切细节,但据我了解,一旦您执行了查询,您将需要读取查询产生的所有行;但我对此不是 100% 确定。
      • 根据文档,它应该是这样工作的,但是它必须与不限制结果集的查询一样执行。在实践中,性能更接近于使用 LIMIT。所以,我猜他们会做一些优化它。
      • 我不知道 MySQL 的确切细节,也许没有必要读取所有行,当他们获取 maxRows 时,他们可以通知服务器端游标关闭。那个或者可能驱动程序重写查询以在查询中包含限制。
      【解决方案3】:

      Oracle 使用生产者-消费者设计模式。因此,行是在客户端开始从游标中提取到 ResultSet 时生成的。有两个优化器目标:ALL_ROWS 和 FIRST_ROWS(resp. FIRST_ROWS(n))。当使用 first_rows 优化器目标时,Oracle 倾向于在 hash_joins 上使用更多的嵌套循环,因此它应该更快地返回第一批结果数据。但我不确定使用 setMaxRows 方法是否也会改变查询的优化器目标。

      【讨论】:

        猜你喜欢
        • 2019-09-17
        • 2014-07-30
        • 2016-01-01
        • 1970-01-01
        • 2015-07-23
        • 2014-01-10
        • 2016-08-18
        • 2015-03-23
        • 2012-08-27
        相关资源
        最近更新 更多