web-dev-qa-db-ja.com

JDBCでバッチINSERTSを実行する効率的な方法

私のアプリでは、多くのINSERTを行う必要があります。そのa Java appと私はプレーンJDBCを使用してクエリを実行しています。DBはOracleです。個別のINSERTとして:

insert into some_table (col1, col2) values (val1, val2)
insert into some_table (col1, col2) values (val3, val4)
insert into some_table (col1, col2) values (val5, val6)

次の形式のINSERTの方が効率的かどうか疑問に思っていました。

insert into some_table (col1, col2) values (val1, val2), (val3, val4), (val5, val6)

つまり、複数のINSERTを1つに折りたたみます。

バッチINSERTを高速化するための他のヒントはありますか?

57
Aayush Puri

これは、以前の2つの回答の組み合わせです。

  PreparedStatement ps = c.prepareStatement("INSERT INTO employees VALUES (?, ?)");

  ps.setString(1, "John");
  ps.setString(2,"Doe");
  ps.addBatch();

  ps.clearParameters();
  ps.setString(1, "Dave");
  ps.setString(2,"Smith");
  ps.addBatch();

  ps.clearParameters();
  int[] results = ps.executeBatch();
117
Tusc

質問はJDBCを使用してOracleに効率的に挿入するように求めていますが、私は現在DB2(IBMメインフレーム上)で遊んでいますが、概念的に挿入は似ているので間のメトリックを確認するのに役立つかもしれません

  • 一度に1つのレコードを挿入する

  • レコードのバッチの挿入(非常に効率的)

指標をご覧ください

1)一度に1つのレコードを挿入する

_public void writeWithCompileQuery(int records) {
    PreparedStatement statement;

    try {
        Connection connection = getDatabaseConnection();
        connection.setAutoCommit(true);

        String compiledQuery = "INSERT INTO TESTDB.EMPLOYEE(EMPNO, EMPNM, DEPT, RANK, USERNAME)" +
                " VALUES" + "(?, ?, ?, ?, ?)";
        statement = connection.prepareStatement(compiledQuery);

        long start = System.currentTimeMillis();

        for(int index = 1; index < records; index++) {
            statement.setInt(1, index);
            statement.setString(2, "emp number-"+index);
            statement.setInt(3, index);
            statement.setInt(4, index);
            statement.setString(5, "username");

            long startInternal = System.currentTimeMillis();
            statement.executeUpdate();
            System.out.println("each transaction time taken = " + (System.currentTimeMillis() - startInternal) + " ms");
        }

        long end = System.currentTimeMillis();
        System.out.println("total time taken = " + (end - start) + " ms");
        System.out.println("avg total time taken = " + (end - start)/ records + " ms");

        statement.close();
        connection.close();

    } catch (SQLException ex) {
        System.err.println("SQLException information");
        while (ex != null) {
            System.err.println("Error msg: " + ex.getMessage());
            ex = ex.getNextException();
        }
    }
}
_

100トランザクションのメトリック:

_each transaction time taken = 123 ms
each transaction time taken = 53 ms
each transaction time taken = 48 ms
each transaction time taken = 48 ms
each transaction time taken = 49 ms
each transaction time taken = 49 ms
...
..
.
each transaction time taken = 49 ms
each transaction time taken = 49 ms
total time taken = 4935 ms
avg total time taken = 49 ms
_

最初のトランザクションは_120-150ms_を使用しています。これは クエリ解析 であり、その後の実行では、後続のトランザクションは_50ms_のみを使用しています。 (まだ高いですが、私のデータベースは別のサーバーにあります(ネットワークのトラブルシューティングが必要です))

2)バッチに挿入する場合(効率的な1つ)-preparedStatement.executeBatch()により実現

_public int[] writeInABatchWithCompiledQuery(int records) {
    PreparedStatement preparedStatement;

    try {
        Connection connection = getDatabaseConnection();
        connection.setAutoCommit(true);

        String compiledQuery = "INSERT INTO TESTDB.EMPLOYEE(EMPNO, EMPNM, DEPT, RANK, USERNAME)" +
                " VALUES" + "(?, ?, ?, ?, ?)";
        preparedStatement = connection.prepareStatement(compiledQuery);

        for(int index = 1; index <= records; index++) {
            preparedStatement.setInt(1, index);
            preparedStatement.setString(2, "empo number-"+index);
            preparedStatement.setInt(3, index+100);
            preparedStatement.setInt(4, index+200);
            preparedStatement.setString(5, "usernames");
            preparedStatement.addBatch();
        }

        long start = System.currentTimeMillis();
        int[] inserted = preparedStatement.executeBatch();
        long end = System.currentTimeMillis();

        System.out.println("total time taken to insert the batch = " + (end - start) + " ms");
        System.out.println("total time taken = " + (end - start)/records + " s");

        preparedStatement.close();
        connection.close();

        return inserted;

    } catch (SQLException ex) {
        System.err.println("SQLException information");
        while (ex != null) {
            System.err.println("Error msg: " + ex.getMessage());
            ex = ex.getNextException();
        }
        throw new RuntimeException("Error");
    }
}
_

100トランザクションのバッチのメトリックは次のとおりです。

_total time taken to insert the batch = 127 ms
_

1000件のトランザクション

_total time taken to insert the batch = 341 ms
_

したがって、_~5000ms_(一度に1つのtrxnを使用)で100トランザクションを作成すると、_~150ms_(100レコードのバッチ)に減少します。

注-ネットワークは無視してください。これは非常に遅いですが、メトリック値は相対的です。

20
prayagupd

Statementには、次のオプションがあります。

Statement stmt = con.createStatement();

stmt.addBatch("INSERT INTO employees VALUES (1000, 'Joe Jones')");
stmt.addBatch("INSERT INTO departments VALUES (260, 'Shoe')");
stmt.addBatch("INSERT INTO emp_dept VALUES (1000, 260)");

// submit a batch of update commands for execution
int[] updateCounts = stmt.executeBatch();
6
Bozho

もちろん、ベンチマークを行う必要がありますが、JDBCでは、ステートメントではなくPreparedStatementを使用すると、複数の挿入を発行する方がはるかに高速になります。

4
Burleigh Bear

このrewriteBatchedStatementsパラメーターを使用して、バッチ挿入をさらに高速化できます。

パラメーターについてはこちらをご覧ください: MySQLとJDBC with rewriteBatchedStatements = true

1
Alex Stanovsky

Javaのバッチ挿入にaddBatchおよびexecuteBatchを使用できます。例を参照してください: Batch Insert In Java

0
user1454294

INSERT ALLステートメントを使用してはどうですか?

INSERT ALL

INTO table_name VALUES ()

INTO table_name VALUES ()

...

SELECT Statement;

このリクエストを成功させるには、最後の選択ステートメントが必須であることを覚えています。しかし、なぜ覚えていない。代わりにPreparedStatementを使用することも検討できます。多くの利点!

ファリド

0
Farid

私のコードでは、「preparedStatement」に直接アクセスできないため、バッチを使用できません。クエリとパラメーターのリストを渡すだけです。ただし、コツは可変長の挿入ステートメントとパラメーターのLinkedListを作成することです。効果は、可変パラメーター入力長を使用した上の例と同じです。以下を参照(エラー検査は省略)。 「myTable」には3つの更新可能なフィールドがあります:f1、f2、f3

String []args={"A","B","C", "X","Y","Z" }; // etc, input list of triplets
final String QUERY="INSERT INTO [myTable] (f1,f2,f3) values ";
LinkedList params=new LinkedList();
String comma="";
StringBuilder q=QUERY;
for(int nl=0; nl< args.length; nl+=3 ) { // args is a list of triplets values
    params.add(args[nl]);
    params.add(args[nl+1]);
    params.add(args[nl+2]);
    q.append(comma+"(?,?,?)");
    comma=",";
}      
int nr=insertIntoDB(q, params);

私のDBInterfaceクラスには次のものがあります:

int insertIntoDB(String query, LinkedList <String>params) {
    preparedUPDStmt = connectionSQL.prepareStatement(query);
    int n=1;
    for(String x:params) {
        preparedUPDStmt.setString(n++, x);
    }
    int updates=preparedUPDStmt.executeUpdate();
    return updates;
}
0
user3211098