如何从巨大的表中读取所有行?

mar*_*osh 48 java postgresql jdbc

处理数据库中的所有行(PostgreSQL)时遇到问题.我得到一个错误:org.postgresql.util.PSQLException: Ran out of memory retrieving query results.我认为我需要读取所有行的小块,但它不起作用 - 它只读取100行(下面的代码).怎么做?

    int i = 0;      
    Statement s = connection.createStatement();
    s.setMaxRows(100); // bacause of: org.postgresql.util.PSQLException: Ran out of memory retrieving query results.
    ResultSet rs = s.executeQuery("select * from " + tabName);      
    for (;;) {
        while (rs.next()) {
            i++;
            // do something...
        }
        if ((s.getMoreResults() == false) && (s.getUpdateCount() == -1)) {
            break;
        }           
    }
Run Code Online (Sandbox Code Playgroud)

nos*_*nos 64

短版本是,调用stmt.setFetchSize(50);conn.setAutoCommit(false);避免将整个内容读ResultSet入内存.

以下是文档所说的内容:

根据游标获取结果

默认情况下,驱动程序立即收集查询的所有结果.这对于大型数据集来说可能不方便,因此JDBC驱动程序提供了一种将ResultSet基于数据库游标并仅获取少量行的方法.

在连接的客户端缓存少量行,当用尽时,通过重新定位光标来检索下一行行.

注意:

  • 基于游标的ResultSet不能在所有情况下使用.有许多限制会使驱动程序无声地回退到同时获取整个ResultSet.

  • 与服务器的连接必须使用V3协议.这是服务器版本7.4及更高版本的默认设置(仅受支持).-

  • Connection不能处于自动提交模式.后端在事务结束时关闭游标,因此在自动提交模式下,后端将关闭游标,然后才能从中获取任何内容.-

  • 必须使用ResultSet类型ResultSet.TYPE_FORWARD_ONLY创建Statement.这是默认值,因此不需要重写代码以利用此功能,但这也意味着您无法向后滚动或以其他方式在ResultSet中跳转.-

  • 给出的查询必须是单个语句,而不是与分号串在一起的多个语句.

例5.2.设置提取大小以打开和关闭游标.

将代码更改为游标模式就像将Statement的获取大小设置为适当的大小一样简单.将获取大小设置为0将导致所有行被缓存(默认行为).

// make sure autocommit is off
conn.setAutoCommit(false);
Statement st = conn.createStatement();

// Turn use of the cursor on.
st.setFetchSize(50);
ResultSet rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
   System.out.print("a row was returned.");
}
rs.close();

// Turn the cursor off.
st.setFetchSize(0);
rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
   System.out.print("many rows were returned.");
}
rs.close();

// Close the statement.
st.close();
Run Code Online (Sandbox Code Playgroud)


Fra*_*ens 38

在PostgreSQL中使用CURSOR让JDBC驱动程序为您处理.

处理大型数据集时,LIMIT和OFFSET会变慢.


rog*_*ack 6

因此,问题的症结在于默认情况下,Postgres以“ autoCommit”模式启动,并且还需要/使用游标才能“分页”数据(例如:读取前10K个结果,然后读取接下来,接着是下一个),但是游标只能存在于事务中。因此默认设置是始终将所有行读入RAM,然后让程序在全部到达之后开始处理“第一个结果行,然后第二个结果行”,由于两个原因,它不在事务中(因此,游标(无法正常工作),并且尚未设置抓取大小。

因此,psql命令行工具如何实现FETCH_COUNT查询的批处理响应(其设置)是在短期事务(如果尚未打开事务)中“包装”其选择的查询,以便游标可以工作。您也可以使用JDBC做类似的事情:

  static void readLargeQueryInChunksJdbcWay(Connection conn, String originalQuery, int fetchCount, ConsumerWithException<ResultSet, SQLException> consumer) throws SQLException {
    boolean originalAutoCommit = conn.getAutoCommit();
    if (originalAutoCommit) {
      conn.setAutoCommit(false); // start temp transaction
    }
    try (Statement statement = conn.createStatement()) {
      statement.setFetchSize(fetchCount);
      ResultSet rs = statement.executeQuery(originalQuery);
      while (rs.next()) {
        consumer.accept(rs); // or just do you work here
      }
    } finally {
      if (originalAutoCommit) {
        conn.setAutoCommit(true); // reset it, also ends (commits) temp transaction
      }
    }
  }
  @FunctionalInterface
  public interface ConsumerWithException<T, E extends Exception> {
    void accept(T t) throws E;
  }
Run Code Online (Sandbox Code Playgroud)

这带来了需要更少RAM的好处,而且即使您不需要节省RAM,我的结果也是整体上看运行更快。奇怪的。它还带来的好处是您对第一行的处理“启动更快”(因为它一次处理一页)。

这就是“原始postgres游标”方式以及完整的演示代码的实现方式,尽管在我的实验中,无论出于何种原因,上述JDBC方式似乎都稍快一些。

另一个选择是在所有位置都autoCommit关闭模式,尽管您仍然必须始终为每个新语句手动指定fetchSize(或者您可以在URL字符串中设置默认的提取大小)。