毎日数百万件のレコードを持つこの非常に大きなテーブルがあり、毎日の終わりに前日のすべてのレコードを抽出しています。私はこれを次のようにやっています:
_String SQL = "select col1, col2, coln from mytable where timecol = yesterday";
Statement.executeQuery(SQL);
_
問題は、このプログラムがすべての結果をメモリに取得して処理するため、2GBのメモリを消費することです。
Statement.setFetchSize(10)
を設定しようとしましたが、OSとまったく同じメモリを使用しますが、違いはありません。このためにMicrosoft SQL Server 2005 JDBC Driverを使用しています。
クエリを実行して数行のみを表示し、スクロールダウンしてより多くの結果が表示される場合、Oracleデータベースドライバーのように、結果を小さなチャンクで読み取る方法はありますか?
JDBCでは、setFetchSize(int)
メソッドはJVMからデータベースへのネットワーク呼び出しの数とそれに対応するRAM ResultSet処理に使用されます。
本質的にsetFetchSize(10)が呼び出され、ドライバーがそれを無視している場合、おそらく2つのオプションしかありません。
RESULT-SETは、クエリへの応答としてDB上で整列化された行の数です。 ROW-SETは、JVMからDBへの呼び出しごとにRESULT-SETからフェッチされる行のチャンクです。これらの呼び出しの数と結果のRAM処理に必要)は、fetch-size設定に依存します。
したがって、RESULT-SETに100行があり、fetch-sizeが10である場合、およそ10 * {row-content-size} RAM =いつでも。
デフォルトのfetch-sizeは10で、かなり小さいです。投稿されたケースでは、ドライバーはフェッチサイズ設定を無視し、1回の呼び出しですべてのデータを取得しているように見えます(大規模なRAM要件、最適な最小ネットワーク呼び出し)。
ResultSet.next()
の下で起こるのは、RESULT-SETから一度に1行を実際にフェッチしないということです。 (ローカル)ROW-SETからそれをフェッチし、ローカルクライアントで使い果たされると、次のROW-SETをサーバーから(見えないように)フェッチします。
設定は単なる「ヒント」であるため、これらはすべてドライバーに依存しますが、実際には、これが多くのドライバーおよびデータベース(Oracle、DB2、およびMySQLの多くのバージョンで検証済み)でどのように機能するかがわかりました。
fetchSize
パラメーターは、データベースから一度にフェッチする多くの行に関するJDBCドライバーに対するヒントです。しかし、ドライバーはこれを無視して、適切と思われるものを自由に実行できます。 Oracleなどの一部のドライバーは行をチャンクでフェッチするため、大量のメモリを必要とせずに非常に大きな結果セットを読み取ることができます。他のドライバーは結果セット全体を一度に読み込むだけであり、それがあなたのドライバーが行っていることだと推測しています。
ドライバーをSQL Server 2008バージョン(より良いかもしれません)、またはオープンソースのjTDSドライバーにアップグレードしてみてください。
接続の自動コミットが有効になっていることを確認する必要がありますoff、またはsetFetchSizeは効果がありません。
dbConnection.setAutoCommit(false);
編集:この修正を使用したとき、それはPostgres固有のものでしたが、うまくいけばSQL Serverでも機能することを願っています。
ステートメントインターフェイス Doc
概要:
void setFetchSize(int rows)
は、JDBCドライバーに、さらに行が必要なときにデータベースからフェッチする行数に関するヒントを提供します。
この電子ブックを読む J2EE以降、Art Taylor氏
Mssql jdbcが結果セット全体をバッファリングしているように聞こえます。 selectMode = cursorまたはresponseBuffering = adaptiveを示す接続文字列パラメーターを追加できます。 2005 mssql jdbcドライバのバージョン2.0以降を使用している場合、応答バッファリングはデフォルトでアダプティブになります。
本当にクエリで返される行を制限し、結果をページングしたいようです。その場合、次のようなことができます:
select * from (select rownum myrow, a.* from TEST1 a )
where myrow between 5 and 10 ;
あなたの境界を決定する必要があります。
これを試して:
String SQL = "select col1, col2, coln from mytable where timecol = yesterday";
connection.setAutoCommit(false);
PreparedStatement stmt = connection.prepareStatement(SQL, SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY, SQLServerResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(2000);
stmt.set....
stmt.execute();
ResultSet rset = stmt.getResultSet();
while (rset.next()) {
// ......
プロジェクトでもまったく同じ問題がありました。問題は、フェッチサイズが十分に小さい場合でも、JDBCTemplateがクエリのすべての結果を読み取り、メモリを消費する可能性のある巨大なリストにマップすることです。最終的にNamedParameterJdbcTemplateを拡張して、オブジェクトのストリームを返す関数を作成しました。そのStreamはJDBCによって通常返されるResultSetに基づいていますが、Streamが必要とする場合にのみResultSetからデータをプルします。このストリームが吐き出すすべてのオブジェクトの参照を保持しない場合、これは機能します。私はorg.springframework.jdbc.core.JdbcTemplate#execute(org.springframework.jdbc.core.ConnectionCallback)の実装に大きな影響を与えました。唯一の本当の違いは、ResultSetをどうするかということです。 ResultSetをラップするためにこの関数を書くことになりました。
private <T> Stream<T> wrapIntoStream(ResultSet rs, RowMapper<T> mapper) {
CustomSpliterator<T> spliterator = new CustomSpliterator<T>(rs, mapper, Long.MAX_VALUE, NON-NULL | IMMUTABLE | ORDERED);
Stream<T> stream = StreamSupport.stream(spliterator, false);
return stream;
}
private static class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
// won't put code for constructor or properties here
// the idea is to pull for the ResultSet and set into the Stream
@Override
public boolean tryAdvance(Consumer<? super T> action) {
try {
// you can add some logic to close the stream/Resultset automatically
if(rs.next()) {
T mapped = mapper.mapRow(rs, rowNumber++);
action.accept(mapped);
return true;
} else {
return false;
}
} catch (SQLException) {
// do something with this Exception
}
}
}
そのストリームを「自動クローズ可能」にするためのロジックを追加できます。そうでない場合は、完了時に忘れずに閉じるようにしてください。