web-dev-qa-db-ja.com

MySQLを使用した大きな結果セットのストリーミング

大規模なMySQLテーブルを使用するSpringアプリケーションを開発しています。大きなテーブルをロードすると、ドライバーがテーブル全体をアプリケーションメモリにロードしようとするため、OutOfMemoryExceptionが返されます。

使ってみた

_statement.setFetchSize(Integer.MIN_VALUE);
_

しかし、開いたすべてのResultSetはclose();でハングします。オンラインで見ると、ResultSetを閉じる前に未読行をロードしようとするために起こることがわかりましたが、私はこれを行うのでそうではありません:

_ResultSet existingRecords = getTableData(tablename);
try {
    while (existingRecords.next()) {
        // ...
    }
} finally {
    existingRecords.close(); // this line is hanging, and there was no exception in the try clause
}
_

ハングは小さなテーブル(3行)でも発生し、RecordSet(1つのメソッドで発生した)を閉じないと、connection.close()がハングします。


ハングのスタックトレース:

SocketInputStream.socketRead0(FileDescriptor、byte []、int、int、int)line:使用不可[ネイティブメソッド]
SocketInputStream.read(byte []、int、int)行:129
ReadAheadInputStream.fill(int)行:113
ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(byte []、int、int)行:160
ReadAheadInputStream.read(byte []、int、int)行:188
MysqlIO.readFully(InputStream、byte []、int、int)行:2428 MysqlIO.reuseAndReadPacket(Buffer、int)行:2882
MysqlIO.reuseAndReadPacket(Buffer)行:2871
MysqlIO.checkErrorPacket(int)行:3414
MysqlIO.checkErrorPacket()行:910
MysqlIO.nextRow(Field []、int、boolean、int、boolean、boolean、boolean、Buffer)行:1405
RowDataDynamic.nextRecord()行:413
RowDataDynamic.next()行:392 RowDataDynamic.close()行:170
JDBC4ResultSet(ResultSetImpl).realClose(boolean)行:7473 JDBC4ResultSet(ResultSetImpl).close()行:881 DelegatingResultSet.close()行:152
DelegatingResultSet.close()行:152
DelegatingPreparedStatement(DelegatingStatement).close()行:163
(これは私のクラスです)Database.close()行:84

43
configurator

ResultSetsを2回閉じないでください。

明らかに、Statementを閉じると、対応するResultSetを閉じようとします。これは、スタックトレースの次の2行で見ることができます。

DelegatingResultSet.close()行:152
DelegatingPreparedStatement(DelegatingStatement).close()行:163

ハングはResultSet.close()にあると思っていましたが、実際にはStatement.close()を呼び出すResultSet.close()にありました。 ResultSetは既に閉じられているため、ハングしました。

すべてのResultSet.close()results.getStatement().close()に置き換え、すべてのStatement.close() sを削除し、問題は解決しました。

12
configurator

フェッチサイズの設定のみが正しいアプローチではありません。 Statement#setFetchSize()のjavadoc はすでに次のことを述べています:

JDBCドライバーに、データベースからフェッチする行数に関してhintを与える

ドライバーは実際にヒントを自由に適用または無視できます。一部のドライバーはそれを無視し、一部のドライバーはそれを直接適用し、一部のドライバーはさらにパラメーターを必要とします。 MySQL JDBCドライバーは最後のカテゴリーに分類されます。 MySQL JDBCドライバーのドキュメント を確認すると、次の情報が表示されます(ヘッダーまで2/3スクロールダウンします)ResultSet)):

この機能を有効にするには、次の方法でStatementインスタンスを作成する必要があります。

stmt = conn.createStatement(Java.sql.ResultSet.TYPE_FORWARD_ONLY, Java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

ドキュメントのentireセクションを読んでください。このアプローチの注意点についても説明しています。関連する引用文献は次のとおりです。

このアプローチにはいくつかの注意事項があります。接続で他のクエリを発行する前に、結果セットのすべての行を読み取る(または閉じる)必要があります。そうしないと、例外がスローされます。

(...)

ステートメントがトランザクションの範囲内にある場合、トランザクションが完了するとロックが解除されます(これは、ステートメントが最初に完了する必要があることを意味します)。他のほとんどのデータベースと同様に、ステートメントで保留中のすべての結果が読み取られるか、ステートメントのアクティブな結果セットが閉じられるまで、ステートメントは完了しません。

それでもOutOfMemoryErrorExceptionではなく)が修正されない場合、問題はおそらくすべてのデータを処理するのではなくJavaのメモリに格納していることですデータが入ったらすぐに。これには、コードをさらに変更する必要があります。完全に書き直す必要があります。 here の前に同様の質問に答えました。

56
BalusC

誰かが同じ問題を抱えている場合、クエリでLIMIT句を使用して解決しました。

この問題はバグとしてMySqlに報告されました(ここで見つけてください http://bugs.mysql.com/bug.php?id=42929 )。これは現在「not a bug」のステータスになっています。最も適切な部分は次のとおりです。

現在、結果セット「midstream」を閉じる方法はありません

すべての行を読み取る必要があるため、WHEREやLIMITなどの句を使用してクエリ結果を制限する必要があります。または、次を試してください。

ResultSet rs = ...
while(rs.next()) {
   ...
   if(bailOut == true) { break; }
}

while(rs.next()); // This will deplete the remaining rows on the stream

rs.close();

それは理想的ではないかもしれませんが、少なくともそれはあなたが近くでハングを乗り越えるようにします。

4
Rooney

Spring jdbcを使用している場合は、simpleJdbcTemplateとともにpreparestatementクリエーターを使用して、fetchSizeをInteger.MIN_VALUEとして設定する必要があります。ここで説明 http://neopatel.blogspot.com/2012/02/mysql-jdbc-driver-and-streaming-large.html

1
kalpesh

リスニングを停止しても、リクエストは継続するため、ハングします。 ResultSetとStatementを正しい順序で閉じるには、最初にstatement.cancel()を呼び出してみてください。

public void close() {
    try {
        statement.cancel();
        if (resultSet != null)
            resultSet.close();
    } catch (SQLException e) {
        // ignore errors on closing
    } finally {
        try {
            statement.close();
        } catch (SQLException e) {
            // ignore errors on closing
        } finally {
            resultSet = null;
            statement = null;
        }
    }
}
0
loic.jaouen