【问题标题】:How to read all rows from huge table?如何从大表中读取所有行?
【发布时间】:2011-04-10 14:28:59
【问题描述】:

我在处理数据库 (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;
        }           
    }

【问题讨论】:

    标签: java postgresql jdbc


    【解决方案1】:

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

    docs 是这样说的:

    根据光标获取结果

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

    在连接的客户端缓存少量行 并且当用尽时,下一个行块由 重新定位光标。

    注意:

    • 不能在所有情况下都使用基于游标的结果集。有一些限制会让司机保持沉默 回退到一次获取整个 ResultSet。

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

    • 连接不得处于自动提交模式。后端在事务结束时关闭游标,因此处于自动提交模式 后端将在任何事情发生之前关闭光标 从中获取。-

    • 必须使用 ResultSet.TYPE_FORWARD_ONLY 的 ResultSet 类型创建语句。这是默认设置,所以没有代码 需要重写以利用这一点,但它也 意味着您不能向后滚动或以其他方式跳来跳去 在结果集中。-

    • 给定的查询必须是单个语句,而不是用分号串在一起的多个语句。

    示例 5.2。设置获取大小以打开和关闭光标。

    将代码更改为游标模式就像将语句的获取大小设置为适当的大小一样简单。将提取大小设置回 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();
    

    【讨论】:

    • 这样做有什么缺点吗?我是否应该为所有查询启用它(从文档中的措辞来看,它在所有情况下听起来都很棒;如果您正在阅读大表,那会更好,如果您正在阅读小表,那没关系)
    【解决方案2】:

    使用a CURSOR in PostgreSQLlet the JDBC-driver handle this for you

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

    【讨论】:

    【解决方案3】:

    所以问题的症结在于默认情况下,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 光标”的方式以及完整的演示 code,尽管在我的实验中,无论出于何种原因,上面的 JDBC 方式似乎稍快一些。

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

    【讨论】:

      【解决方案4】:

      我认为您的问题类似于此线程:JDBC Pagination,其中包含满足您需求的解决方案。

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

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

      【讨论】:

      【解决方案5】:

      我是这样做的。不是我认为最好的方式,但它有效:)

          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;
              }
          }
      

      【讨论】:

        【解决方案6】:

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

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

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-02
          • 1970-01-01
          相关资源
          最近更新 更多