本文目录导读:

JDBC批处理是提升大量数据插入效率的关键技术,它通过减少网络往返次数和数据库解析开销来显著提高性能。
批处理的核心原理
普通插入:每次执行executeUpdate()都会:
- 发送SQL到数据库
- 数据库解析SQL
- 执行插入
- 返回结果
批处理:将多条SQL收集后一次性发送:
- 客户端缓存SQL
- 一次性发送到数据库
- 数据库批量解析执行
- 返回批量结果
性能提升可达10-100倍,数据量越大效果越明显。
批处理实现示例
基础批处理代码
public class BatchInsertExample {
private static final String INSERT_SQL =
"INSERT INTO users (name, age, email) VALUES (?, ?, ?)";
private static final int BATCH_SIZE = 1000;
public void batchInsert(List<User> userList) {
String url = "jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true";
try (Connection conn = DriverManager.getConnection(url, "root", "password");
PreparedStatement ps = conn.prepareStatement(INSERT_SQL)) {
// 关闭自动提交
conn.setAutoCommit(false);
int count = 0;
for (User user : userList) {
ps.setString(1, user.getName());
ps.setInt(2, user.getAge());
ps.setString(3, user.getEmail());
ps.addBatch();
count++;
// 每BATCH_SIZE条执行一次批处理
if (count % BATCH_SIZE == 0) {
ps.executeBatch();
conn.commit(); // 提交事务
ps.clearBatch();
}
}
// 处理剩余数据
if (count % BATCH_SIZE != 0) {
ps.executeBatch();
conn.commit();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
完整的工具类实现
public class JdbcBatchHelper {
/**
* 批量插入数据
* @param connection 数据库连接
* @param sql SQL语句
* @param batchData 数据列表
* @param batchSize 每批大小
*/
public static <T> int batchInsert(
Connection connection,
String sql,
List<T> batchData,
int batchSize,
BatchSetter<T> setter) throws SQLException {
int totalCount = 0;
boolean originalAutoCommit = connection.getAutoCommit();
try {
// 关闭自动提交
connection.setAutoCommit(false);
try (PreparedStatement ps = connection.prepareStatement(sql)) {
for (int i = 0; i < batchData.size(); i++) {
// 设置参数
setter.setValues(ps, batchData.get(i));
ps.addBatch();
// 批量执行
if ((i + 1) % batchSize == 0 || i == batchData.size() - 1) {
int[] results = ps.executeBatch();
connection.commit();
ps.clearBatch();
// 统计成功插入数
for (int result : results) {
if (result > 0) {
totalCount += result;
}
}
}
}
}
return totalCount;
} catch (SQLException e) {
connection.rollback(); // 发生错误时回滚
throw e;
} finally {
// 恢复自动提交设置
connection.setAutoCommit(originalAutoCommit);
}
}
// 函数式接口
@FunctionalInterface
public interface BatchSetter<T> {
void setValues(PreparedStatement ps, T data) throws SQLException;
}
}
使用示例
// 使用工具类
List<User> users = getUserData();
String sql = "INSERT INTO users (name, age, email) VALUES (?, ?, ?)";
int insertedCount = JdbcBatchHelper.batchInsert(
connection,
sql,
users,
500, // 每批500条
(ps, user) -> {
ps.setString(1, user.getName());
ps.setInt(2, user.getAge());
ps.setString(3, user.getEmail());
}
);
性能优化关键点
数据库连接URL配置
MySQL需添加rewriteBatchedStatements=true:
// 必须添加此参数,否则批处理不会生效
String url = "jdbc:mysql://localhost:3306/test"
+ "?rewriteBatchedStatements=true"
+ "&useServerPrepStmts=false"; // 建议关闭服务器端预编译
批处理大小选择
// 经验值:500-2000条/批 // 需要根据实际情况测试 public static final int BATCH_SIZE = 1000;
| 数据量 | 批大小 | 性能表现 |
|---|---|---|
| <1000 | 全部 | 直接提交 |
| 1万-10万 | 500-1000 | 最佳 |
| 10万-100万 | 1000-2000 | 内存与性能平衡 |
| >100万 | 2000-5000 | 需关注内存 |
事务管理
// 必须手动管理事务
conn.setAutoCommit(false);
// 每批提交一次
try {
// ...
ps.executeBatch();
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}
性能对比测试
public class PerformanceTest {
public static void testInsertPerformance() throws SQLException {
int recordCount = 100000; // 10万条数据
// 1. 普通插入(逐条)
long startTime = System.currentTimeMillis();
normalInsert(recordCount);
long normalTime = System.currentTimeMillis() - startTime;
// 2. 批处理插入
startTime = System.currentTimeMillis();
batchInsert(recordCount);
long batchTime = System.currentTimeMillis() - startTime;
System.out.println("普通插入耗时: " + normalTime + "ms");
System.out.println("批处理耗时: " + batchTime + "ms");
System.out.println("性能提升: " + (normalTime / (double)batchTime) + "倍");
}
}
典型测试结果(MySQL 8.0,本地数据库):
| 数据量 | 普通插入 | 批处理 | 提升倍数 |
|---|---|---|---|
| 1万 | 5s | 3s | 28x |
| 10万 | 89s | 1s | 42x |
| 100万 | 无法完成(超时) | 18s |
高级优化技巧
多表批量插入
// 使用同一个Connection进行多表批处理
try {
String sql1 = "INSERT INTO table1 ...";
String sql2 = "INSERT INTO table2 ...";
try (PreparedStatement ps1 = conn.prepareStatement(sql1);
PreparedStatement ps2 = conn.prepareStatement(sql2)) {
for (Record record : records) {
// 表1的批处理
ps1.setXXX(...);
ps1.addBatch();
// 表2的批处理
ps2.setXXX(...);
ps2.addBatch();
if (count % BATCH_SIZE == 0) {
ps1.executeBatch();
ps2.executeBatch();
conn.commit();
}
}
}
} catch (SQLException e) {
conn.rollback();
}
使用批量插入SQL
// 对于少量数据,可以使用单个批量INSERT语句
String bulkSql = "INSERT INTO users (name, age) VALUES " +
"('张三', 25), ('李四', 30), ('王五', 28)";
// 这种方式适用于100条以内的数据
异步批处理
// 使用线程池进行并发批处理
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Callable<Integer>> tasks = new ArrayList<>();
// 将大数据集分片
List<List<User>> batches = partition(users, BATCH_SIZE);
for (List<User> batch : batches) {
tasks.add(() -> {
try (Connection conn = dataSource.getConnection()) {
return batchInsert(conn, batch);
}
});
}
// 并行执行
List<Future<Integer>> results = executor.invokeAll(tasks);
常见问题与解决方案
批处理未生效
// 检查MySQL连接URL String url = "jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true"; // 验证批处理是否生效 // 开启MySQL通用日志,观察是否将多条SQL合并为一条执行
内存溢出
// 控制批处理大小 // 每批数据不要太大,通常1000-5000条 // 及时clearBatch()释放内存 ps.clearBatch(); // 监控JVM内存使用 Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
主键冲突处理
// 使用INSERT IGNORE或REPLACE INTO
String sql = "INSERT IGNORE INTO users (id, name) VALUES (?, ?)";
// 或
String sql = "REPLACE INTO users (id, name) VALUES (?, ?)";
// 或者捕获异常跳过冲突记录
try {
ps.executeBatch();
} catch (BatchUpdateException e) {
// 处理部分失败的情况
int[] updateCounts = e.getUpdateCounts();
}
- 必须添加
rewriteBatchedStatements=true - 关闭自动提交
conn.setAutoCommit(false) - 选择合适批大小 500-2000条
- 定期提交和清理 避免内存溢出
- 异常处理 实现事务回滚
- 资源管理 使用try-with-resources
- 性能监控 对比测试找到最佳配置
通过合理使用JDBC批处理,可以将插入效率提升10-50倍,对于大数据量场景(如数据迁移、日志导入)效果尤为显著。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播