向數據庫發送大量 SQL 時,逐條 executeUpdate() 會產生嚴重網絡往返開銷。JDBC Batch 機制允許**客戶端攒批、一次性發送**,大幅降低網絡延遲。本文詳細演示 Statement 與 PreparedStatement 兩種批處理模式的代碼實現、適用場景、性能差異與生產級優化參數。
public class StatementBatchDemo {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/java", "root", "root");
Statement stmt = conn.createStatement();
// 關鍵:關閉自動提交,事務手動控制
conn.setAutoCommit(false);
// 可添加**完全不同結構**的 SQL
stmt.addBatch("CREATE DATABASE IF NOT EXISTS batch_db");
stmt.addBatch("USE batch_db");
stmt.addBatch("CREATE TABLE t_batch (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20))");
stmt.addBatch("INSERT INTO t_batch(name) VALUES('aaa')");
stmt.addBatch("INSERT INTO t_batch(name) VALUES('bbb')");
stmt.addBatch("INSERT INTO t_batch(name) VALUES('ccc')");
// 一次性執行批次,返回每條 SQL 影響行數數組
int[] results = stmt.executeBatch();
conn.commit(); // 統一提交事務
System.out.println("批次執行結果: " + Arrays.toString(results));
stmt.close(); conn.close();
} catch (Exception e) { e.printStackTrace(); }
}
}
特點:
public class PreparedStatementBatchDemo {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/java?rewriteBatchedStatements=true", "root", "root");
// 關鍵連接參數:rewriteBatchedStatements=true (MySQL 驅動專用,將批次重寫為多值 INSERT)
conn.setAutoCommit(false);
String sql = "INSERT INTO t_batch(name, age, create_time) VALUES(?, ?, NOW())";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 模擬 10 萬條數據批量入庫
int batchSize = 1000; // 每 1000 條提交一次,防止內存/事務日誌過大
for (int i = 1; i <= 100_000; i++) {
pstmt.setString(1, "user_" + i);
pstmt.setInt(2, 20 + i % 50);
pstmt.addBatch();
if (i % batchSize == 0) {
pstmt.executeBatch(); // 發送批次
conn.commit(); // 提交事務
pstmt.clearBatch(); // 清空批次緩衝
}
}
// 剩餘不足 batchSize 的數據
pstmt.executeBatch();
conn.commit();
pstmt.close(); conn.close();
System.out.println("10萬條數據批量入庫完成");
} catch (Exception e) { e.printStackTrace(); }
}
}
| 維度 | Statement.addBatch | PreparedStatement.addBatch |
|---|---|---|
| SQL 解析 | 每條獨立解析 | **預編譯一次**,多次復用執行計劃 |
| 參數傳遞 | 字符串拼接 (注入風險) | **參數化綁定**,類型安全 |
| 網絡往返 | 批次發送一次 | 批次發送一次 |
| MySQL 優化 | 無 | rewriteBatchedStatements=true 合併為 INSERT ... VALUES(...),(...),(...) |
| 典型性能 (10萬條) | ~30-60 秒 | **~2-5 秒** (快 10-20 倍) |
| 適用場景 | DDL 初始化、異構腳本、一次性遷移 | **業務數據大批量寫入、ETL、日志入庫** |
executeBatch() + commit() + clearBatch(),避免單事務過大導致鎖升級/日志膨脹/內存 OOMconn.setAutoCommit(false),手動控制事務邊界rewriteBatchedStatements=true,驅動層將多條 INSERT 重寫為單條多值 INSERT,極大減少解析開銷AUTO_INCREMENT 鎖爭用,改用雪花算法/UUID/號段模式客戶端生成 IDexecuteBatch() 拋 BatchUpdateException,調用 getUpdateCounts() 獲取部分成功結果,決定重試/回滾策略
// 經驗公式:批次大小 = min(可用內存 / 單條對象大小, 事務日誌容量 / 單條日誌大小, 2000)
// 典型值:
// - 寬表(50+列):500-1000
// - 窄表(10列):1000-2000
// - 極簡表(3列):2000-5000
壓測驗證:固定總數據量(如 100萬),測試不同 batchSize 耗時、內存、CPU,選擇拐點值。
掌握 JDBC 批處理,是從「寫單條 SQL」進化為「工程化數據處理」的關鍵技能。下次遇到「導入 100 萬行 Excel 入庫」,你就知道該怎麼優雅地搞定了。