Java多线程批量数据库操作需分片控制、资源隔离、事务边界清晰、错误可追溯;单批200~500条,线程池大小为CPU核数×1.5~2且不超连接池上限;每任务独占连接与事务,禁用共享有状态对象;INSERT用批量语法,主键用雪花ID等全局唯一方案;结果需聚合监控与异常兜底。

Java 中批量数据库操作在多线程下实现安全并发批处理,关键不在“多开线程”,而在于**分片控制、资源隔离、事务边界清晰、错误可追溯**。盲目增加线程数反而容易引发连接池耗尽、行锁争用、主键冲突或事务不一致等问题。
分批切片 + 固定线程池
把大任务拆成小批次,每批独立执行:
- 单批数据量建议控制在 200~500 条,兼顾网络传输、SQL 解析和锁持有时间
- 线程池大小设为 CPU 核数 × 1.5~2(如 8 核机器配 12~16),且不能超过数据库连接池上限(如 HikariCP 的
maximum-pool-size) - 用
List.subList(start, Math.min(start + batchSize, list.size()))安全切片,避免越界异常 - 推荐使用
Executors.newFixedThreadPool(n),避免无界队列堆积任务
每个任务独占数据库连接与事务
多线程间不能共享 Connection、SqlSession 或未提交的事务上下文:
- Spring 环境下,确保每个 Runnable 在
@Transactional方法内执行,或通过TransactionTemplate显式开启新事务 - 纯 JDBC 场景,必须在线程任务内部调用
DataSource.getConnection()获取新连接,操作完立即close() - MyBatis/MyBatis-Plus 的 Mapper 接口实例可共享,但
SqlSession必须按需创建(不要手动缓存或复用) - 禁用静态变量、单例 Service 中持有 ResultSet、PreparedStatement 等有状态对象
规避锁冲突与主键重复
高并发写入时,数据库层面的冲突比 Java 层更早发生:
立即学习“Java免费学习笔记(深入)”;
- INSERT 优先用批量语法:
INSERT INTO t VALUES (),(),()...,比循环单条快 5~10 倍,也减少锁等待 - UPDATE 若基于非主键条件,考虑先
SELECT ... FOR UPDATE SKIP LOCKED预占记录,防止多个线程读到同一批数据后竞相更新 - 主键由 DB 生成时,避免多线程同时调用
NEXTVAL或UUID.randomUUID();改用雪花 ID、DB 序列预分配段等全局唯一方案 - 业务唯一索引字段(如订单号、手机号)写入前加幂等校验,失败时记录并跳过,而非直接抛异常中断整批
结果聚合与异常兜底
主线程必须能感知所有子任务状态,不能只靠 shutdown 就认为完成:
- 用
CountDownLatch或CompletableFuture.allOf()同步等待全部任务结束 - 每个子任务必须包裹
try-catch,捕获具体异常并记录日志 - 失败批次原始数据建议落库(如
error_batch_log表)或写本地文件,便于人工排查与重试 - 避免在 finally 块中执行可能抛异常的数据库操作(如二次 close),防止掩盖主异常


















