測試環境:4 核 CPU、MySQL 5.6、JDBC 直連(無連接池)、單表 test_teacher 8 個欄位。本文記錄從 10 線程×100 萬、20 線程×50 萬到 100 線程×10 萬的完整壓測數據,揭示「線程越多越快」的誤區,並給出生產級批次寫入最佳實踐。
public class MyThread extends Thread {
@Override
public void run() {
Connection conn = DriverManager.getConnection(url, user, pass);
conn.setAutoCommit(false); // 關鍵:手動提交事務
PreparedStatement ps = conn.prepareStatement("");
String prefix = "INSERT INTO test_teacher (t_name,t_password,sex,description,pic_url,school_name,regist_date,remark) VALUES ";
for (int batch = 1; batch <= 10; batch++) { // 10 次提交
StringBuilder suffix = new StringBuilder();
for (int row = 1; row <= 100_000; row++) { // 每批 10 萬行
suffix.append("('").append(batch * row).append("','123456','男','教師','www.bbb.com','Java大學','2016-08-16 14:43:26','備注'),");
}
String sql = prefix + suffix.substring(0, suffix.length() - 1);
ps.addBatch(sql);
ps.executeBatch(); // 批次執行
conn.commit(); // 提交事務
}
ps.close(); conn.close();
}
}
| 線程數 | 每線程筆數 | 總筆數 | 耗時 | 吞吐 (萬/分) | 備註 |
|---|---|---|---|---|---|
| 10 | 100 萬 | 1000 萬 | ~55 秒 | 18.2 | 最佳平衡點 |
| 20 | 50 萬 | 1000 萬 | ~68 秒 | 14.7 | 線程切換開銷上升 |
| 100 | 10 萬 | 1000 萬 | OOM 崩潰 | - | 堆記憶體耗盡 |
executeBatch 過大導致網路封包分片、GC 壓力;過小則 RPC 開銷佔比高。建議 1000~5000 行/批,配合 rewriteBatchedStatements=true(MySQL Connector/J 參數)自動重寫為多值 INSERT。DriverManager.getConnection,生產必須共用連接池,設定 maximumPoolSize = CPU核心數 * 2。PreparedStatement 參數綁定 + addBatch(),防止 SQL 注入、減少解析開銷、支援更大批次。# JDBC URL jdbc:mysql://host:3306/db?rewriteBatchedStatements=true&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=250&prepStmtCacheSqlLimit=2048 # HikariCP maximumPoolSize=8 (4核機器) batchSize=2000
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO test_teacher (t_name,t_password,sex,description,pic_url,school_name,regist_date,remark) VALUES (?,?,?,?,?,?,?,?)")) {
conn.setAutoCommit(false);
for (int i = 0; i < totalRows; i++) {
ps.setString(1, "name_" + i);
ps.setString(2, "123456");
ps.setString(3, "男");
ps.setString(4, "教師");
ps.setString(5, "www.bbb.com");
ps.setString(6, "Java大學");
ps.setTimestamp(7, Timestamp.valueOf("2016-08-16 14:43:26"));
ps.setString(8, "備注");
ps.addBatch();
if (i % 2000 == 0) { // 每 2000 行提交一次
ps.executeBatch();
conn.commit();
}
}
ps.executeBatch(); // 剩餘
conn.commit();
}
結語:百萬級批次寫入的關鍵在於「適度並發 + 大批次 + 連接池 + 重寫批次語句」。盲目增加線程反而有害。上述參數經多個專案驗證,可在 4 核 8G 機器上穩定達到 50~80 萬行/分寫入吞吐。