[英]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 大型機上),從概念上講,插入將是相似的,因此認為查看我的指標之間可能會有所幫助
一次插入一條記錄
插入一批記錄(非常有效)
這是指標
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
。 (仍然很高,但我的數據庫在不同的服務器上(我需要對網絡進行故障排除))
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.