簡體   English   中英

使用 JDBC 進行批量插入的有效方法

[英]Efficient way to do batch INSERTS with JDBC

在我的應用程序中,我需要做很多插入。 它是一個 Java 應用程序,我使用普通的 JDBC 來執行查詢。 數據庫是 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 合並為一個。

使批量插入更快的任何其他提示?

這是前面兩個答案的混合:

  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();

盡管問題要求使用 JDBC 有效地插入到 Oracle 中,但我目前正在使用 DB2(在 IBM 大型機上),從概念上講,插入將是相似的,因此認為查看我的指標之間可能會有所幫助

  • 一次插入一條記錄

  • 插入一批記錄(非常有效)

這是指標

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 ,用於查詢解析然后執行,后續事務只需要大約50ms (仍然很高,但我的數據庫在不同的服務器上(我需要對網絡進行故障排除))

2) 批量插入(高效的) - 通過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毫秒(一次一個 trxn)內進行 100 次交易減少到~150ms 150 毫秒(一批 100 條記錄)。

注意 - 忽略我的超慢網絡,但指標值是相對的。

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();

顯然,您必須進行基准測試,但是如果您使用 PreparedStatement 而不是 Statement,那么通過 JDBC 發出多個插入會快得多。

您可以使用此rewriteBatchedStatements參數使批量插入更快。

您可以在此處閱讀有關參數的信息: MySQL and JDBC with rewriteBatchedStatements=true

您可以在 Java 中使用 addBatch 和 executeBatch 進行批量插入,請參見示例: Java 中的批量插入

使用 INSERT ALL 語句怎么樣?

INSERT ALL

INTO table_name VALUES ()

INTO table_name VALUES ()

...

SELECT Statement;

我記得為了使這個請求成功,最后一個 select 語句是強制性的。 不記得為什么。 您也可以考慮使用PreparedStatement 很多優點!

法里德

在我的代碼中,我無法直接訪問“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;
}

SQLite:以上答案都是正確的。 對於 SQLite,它有點不同。 沒有什么真正的幫助,即使把它放在一個批次中(有時)也不會提高性能。 在這種情況下,嘗試禁用自動提交並在完成后手動提交(警告!當多個連接同時寫入時,您可能會與這些操作發生沖突)

// connect(), yourList and compiledQuery you have to implement/define beforehand
try (Connection conn = connect()) {
     conn.setAutoCommit(false);
     preparedStatement pstmt = conn.prepareStatement(compiledQuery);
     for(Object o : yourList){
        pstmt.setString(o.toString());
        pstmt.executeUpdate();
        pstmt.getGeneratedKeys(); //if you need the generated keys
     }
     pstmt.close();
     conn.commit();

}

如果您使用 jdbcTemplate 則:

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;

    public int[] batchInsert(List<Book> books) {

        return this.jdbcTemplate.batchUpdate(
            "insert into books (name, price) values(?,?)",
            new BatchPreparedStatementSetter() {

                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    ps.setString(1, books.get(i).getName());
                    ps.setBigDecimal(2, books.get(i).getPrice());
                }

                public int getBatchSize() {
                    return books.size();
                }

            });
    }

或者更高級的配置

  import org.springframework.jdbc.core.JdbcTemplate;
  import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter;

    public int[][] batchInsert(List<Book> books, int batchSize) {

        int[][] updateCounts = jdbcTemplate.batchUpdate(
                "insert into books (name, price) values(?,?)",
                books,
                batchSize,
                new ParameterizedPreparedStatementSetter<Book>() {
                    public void setValues(PreparedStatement ps, Book argument) 
                        throws SQLException {
                        ps.setString(1, argument.getName());
                        ps.setBigDecimal(2, argument.getPrice());
                    }
                });
        return updateCounts;

    }

鏈接到

如果迭代次數較少,則使用 PreparedStatements 將比 Statements 慢得多。 要通過在語句上使用 PrepareStatement 獲得性能優勢,您需要在迭代至少為 50 或更高的循環中使用它。

使用語句批量插入

int a= 100;
            try {
                        for (int i = 0; i < 10; i++) {
                            String insert = "insert into usermaster"
                                    + "("
                                    + "userid"
                                    + ")"
                                    + "values("
                                    + "'" + a + "'"
                                    + ");";
                            statement.addBatch(insert);
                            System.out.println(insert);
                            a++;
                        }
                      dbConnection.commit();
                    } catch (SQLException e) {
                        System.out.println(" Insert Failed");
                        System.out.println(e.getMessage());
                    } finally {

                        if (statement != null) {
                            statement.close();
                        }
                        if (dbConnection != null) {
                            dbConnection.close();
                        }
                    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM