web-dev-qa-db-ja.com

一括挿入Java準備済みステートメントのバッチ更新を使用

ResultSetをJavaに約50,000行の10列で入力し、batchExecutePreparedStatementメソッドを使用して別のテーブルに挿入しようとしています。

プロセスを高速化するために、いくつかの調査を行い、resultSetにデータを読み込む際に、fetchSizeが重要な役割を果たすことを発見しました。

FetchSizeが非常に低いと、サーバーへのアクセスが非常に多くなり、fetchSizeが非常に大きいとネットワークリソースがブロックされる可能性があるため、少し実験して、インフラストラクチャに最適なサイズを設定しました。

このresultSetを読んで、別のデータベースの別のテーブルに挿入するための挿入ステートメントを作成しています。

このようなもの(単なるサンプルであり、実際のコードではありません):

for (i=0 ; i<=50000 ; i++) {
    statement.setString(1, "[email protected]");
    statement.setLong(2, 1);
    statement.addBatch();
}
statement.executeBatch();
  • ExecuteBatchメソッドはすべてのデータを一度に送信しようとしますか?
  • バッチサイズを定義する方法はありますか?
  • 一括挿入のプロセスを高速化するより良い方法はありますか?

一括更新(50,000行10列)中に、バッチ実行で更新可能なResultSetまたはPreparedStaementを使用する方が良いでしょうか?

30
Mrinmoy

あなたの質問に順番に対処します。

  • executeBatchメソッドはすべてのデータを一度に送信しようとしますか?

これはJDBCドライバーごとに異なる可能性がありますが、私が調べた少数のものは各バッチエントリを反復処理し、実行のためにデータベースに毎回引数を準備済みステートメントハンドルと一緒に送信します。つまり、上記の例では、50,000ペアの引数を使用して50,000回の準備済みステートメントが実行されますが、これらの50,000ステップは、時間の節約になる低レベルの「内部ループ」で実行できます。かなり類似したもので、「ユーザーモード」から「カーネルモード」にドロップダウンし、そこで実行ループ全体を実行するようなものです。バッチエントリごとにその下位レベルモードに出入りするコストを節約できます。

  • バッチサイズを定義する方法はありますか

Statement#executeBatch()を介してバッチを実行する前に、50,000個の引数セットをプッシュすることにより、ここで暗黙的に定義しました。バッチサイズが1でも有効です。

  • 一括挿入のプロセスを高速化するより良い方法はありますか?

バッチ挿入の前にトランザクションを明示的に開き、後でコミットすることを検討してください。データベースまたはJDBCドライバーがバッチ内の各挿入ステップの周りにトランザクション境界を課さないようにしてください。 JDBCレイヤーは Connection#setAutoCommit(boolean) メソッドで制御できます。最初に自動コミットモードから接続を外し、次にバッチを投入し、トランザクションを開始し、バッチを実行してから、 Connection#commit()

このアドバイスは、挿入が同時ライターと競合しないことを前提としており、これらのトランザクション境界により、挿入で使用するためにソーステーブルから十分に一貫した値が読み取られることを前提としています。そうでない場合は、速度よりも正確さを優先してください。

  • バッチ実行で更新可能なResultSetまたはPreparedStatementを使用する方が良いですか?

選択したJDBCドライバーを使用したテストに勝るものはありませんが、後者の場合、PreparedStatementStatement#executeBatch()がここで勝つことを期待しています。ステートメントハンドルには、「バッチ引数」のリストまたは配列が関連付けられている場合があります。各エントリは、Statement#executeBatch()Statement#addBatch()(またはStatement#clearBatch())の呼び出しの間に提供される引数セットです。 。リストはaddBatch()を呼び出すたびに大きくなり、executeBatch()を呼び出すまでフラッシュされません。したがって、Statementインスタンスは実際には引数バッファーとして機能します。便宜上、メモリを交換しています(独自の外部引数セットバッファの代わりにStatementインスタンスを使用)。

繰り返しますが、specificJDBCドライバーについて説明していない限り、これらの回答は一般的で投機的であると考えてください。各ドライバーは洗練度が異なり、どの最適化を追求するかによって異なります。

46
seh

バッチは「一度に」実行されます-それがあなたがそれをするように頼んだことです。

1回の呼び出しで50,000を試みるのは少し大きいようです。次のように、1,000の小さなチャンクに分割します。

final int BATCH_SIZE = 1000;
for (int i = 0; i < DATA_SIZE; i++) {
  statement.setString(1, "[email protected]");
  statement.setLong(2, 1);
  statement.addBatch();
  if (i % BATCH_SIZE == BATCH_SIZE - 1)
    statement.executeBatch();
}
if (DATA_SIZE % BATCH_SIZE != 0)
  statement.executeBatch();

50,000行は数秒以上かかることはありません。

14
Bohemian

このテーブルに挿入されるのがDBの1つまたは複数のテーブルのデータだけで、介入なし(結果セットへの変更)の場合、statement.executeUpdate(SQL)INSERT-SELECT ステートメントを実行します。オーバーヘッドがないため、これはより高速です。 DBの外部にデータが送信されることはなく、操作全体がアプリケーションではなくDB上にあります。

1
LINQ Newbee

ログに記録されていない一括更新では、期待どおりのパフォーマンスが得られません。 this を参照してください

0
Lekkie