web-dev-qa-db-ja.com

巨大なテーブルからすべての行を読み取る方法は?

データベース(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;
        }           
    }
48
marioosh

PostgreSQLのCURSOR または JDBCドライバにこれを処理させます を使用します。

大きなデータセットを処理する場合、LIMITおよびOFFSETは遅くなります。

38
Frank Heikens

短いバージョンは、stmt.setFetchSize(50);conn.setAutoCommit(false);を呼び出して、ResultSet全体をメモリに読み込まないようにします。

docs の意味は次のとおりです。

カーソルに基づいて結果を取得する

デフォルトでは、ドライバーはクエリのすべての結果を一度に収集します。これは大きなデータセットには不便な場合があるため、JDBCドライバーはデータベースカーソルに基づいてResultSetを作成し、少数の行のみをフェッチする手段を提供します。

少数の行が接続のクライアント側にキャッシュされ、使い果たされると、カーソルを再配置して次の行のブロックが取得されます。

注意:

  • カーソルベースのResultSetsは、すべての状況で使用できるわけではありません。一度にResultSet全体をフェッチするようにドライバーを静かにフォールバックさせる多くの制限があります。

  • サーバーへの接続にはV3プロトコルを使用する必要があります。これは、サーバーバージョン7.4以降のデフォルトです(サポートされているのはサーバーバージョンのみです)。

  • 接続は自動コミットモードであってはなりません。バックエンドはトランザクションの終了時にカーソルを閉じます。そのため、自動コミットモードでは、バックエンドは何かをフェッチする前にカーソルを閉じます。

  • Statementは、ResultSet.TYPE_FORWARD_ONLYのResultSetタイプで作成する必要があります。これはデフォルトであるため、これを利用するためにコードを書き換える必要はありませんが、逆方向にスクロールしたり、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();

64
nos

したがって、問題の核心は、デフォルトでは、Postgresが「autoCommit」モードで起動し、データを「ページング」できるようにするためにカーソルが必要/使用されることです(例:最初の10K結果を読み取り、次、次に次)、ただし、カーソルはトランザクション内にのみ存在できます。したがって、デフォルトでは、すべての行を常にRAMに読み込み、プログラムがすべての到着後に「最初の結果行、2番目の行」の処理を開始できるようにします。2つの理由で、トランザクションではありません(カーソル動作しません)、フェッチサイズも設定されていません。

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を保存する必要がない場合でも、全体的に高速に実行されるように見えました。奇妙な。また、最初の行の処理が「一度に1ページずつ処理されるため」「より速く開始する」という利点もあります。

そして、完全なデモ code と共に「生のpostgresカーソル」の方法でこれを行う方法を示しますが、私の実験では、上記のJDBCの方法は何らかの理由で少し高速に見えました。

別のオプションは、すべての場所でautoCommitモードをオフにすることです。ただし、新しいステートメントごとに常に手動でfetchSizeを指定する必要があります(または、URLストリングでデフォルトのフェッチサイズを設定できます)。

6
rogerdpack

あなたの質問は次のスレッドに似ていると思います: 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

2
Benoit Courtine

以下のようにしました。私が考える最良の方法ではありませんが、動作します:)

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

少なくとも私の場合、問題は結果を取得しようとするクライアントにありました。

すべての結果を含む.csvを取得したかった。

を使用して解決策を見つけました

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

(dbnameはdbの名前...)、ファイルにリダイレクトします。

0
ntg