繁体   English   中英

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

[英]How to read all rows from huge table?

处理数据库中的所有行(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;
        }           
    }

简短版本是,调用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();

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

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

事实证明,问题的关键在于,默认情况下,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;
  }

这样可以减少RAM的需求,并且在我的结果中,即使您不需要保存RAM,也可以更快地运行。 奇怪的。 它还带来了第一行处理“更快启动”的好处(因为它一次处理一页)。

以下是如何使用“原始postgres游标”方式,以及完整的演示代码 ,尽管在我的实验中,似乎上面的JDBC方式无论出于何种原因都略快。

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

我认为你的问题类似于这个主题: JDBC Pagination包含满足您需求的解决方案。

特别是,对于PostgreSQL,您可以在请求中使用LIMIT和OFFSET关键字: http//www.petefreitag.com/item/451.cfm

PS:在Java代码中,我建议你使用PreparedStatement而不是简单的语句: http//download.oracle.com/javase/tutorial/jdbc/basics/prepared.html

至少在我的情况下,问题出在客户端试图获取结果。

想要获得所有结果的.csv。

我通过使用找到了解决方案

psql -U postgres -d dbname  -c "COPY (SELECT * FROM T) TO STDOUT WITH DELIMITER ','"

(其中dbname是db的名称...)并重定向到文件。

我是这样做的。 不是我想的最好的方式,但它的工作:)

    Connection c = DriverManager.getConnection("jdbc:postgresql://....");
    PreparedStatement s = c.prepareStatement("select * from " + tabName + " where id > ? order by id");
    s.setMaxRows(100);
    int lastId = 0;
    for (;;) {
        s.setInt(1, lastId);
        ResultSet rs = s.executeQuery();

        int lastIdBefore = lastId;
        while (rs.next()) {
            lastId = Integer.parseInt(rs.getObject(1).toString());
            // ...
        }

        if (lastIdBefore == lastId) {
            break;
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM