必须分批,否则易触发内存溢出、连接超时、锁表或服务假死;核心是控制jvm堆内存、网络负载、mysql事务与缓冲区三处压力,并依数据源类型(excel/sql查询/接口接收)选择对应分批策略。

Java 中大批量数据导入数据库必须分批,否则极易触发内存溢出、数据库连接超时、锁表甚至服务假死。核心不是“能不能一次插”,而是“该不该、能不能稳住”。关键在控制三处压力:JVM堆内存、网络传输负载、MySQL事务与缓冲区压力。
按数据源类型选择分批策略
不同来源的数据,分批切入点不同:
-
Excel 文件(如百万行):用 EasyExcel 的
ReadListener流式读取,边读边缓存,达到批次阈值(如 1000 条)立即插入,不等全量加载;避免Workbook加载整表导致 OOM。 -
SQL Server / 其他库查出的数据:不能
SELECT * FROM table一把捞,要用分页游标或OFFSET/LIMIT(小数据量)+ 更推荐WHERE id > ? ORDER BY id LIMIT N(基于主键的游标分页),防止深分页性能坍塌。 - 接口接收的批量数据:上游调用方需配合按固定大小(如 500 条/次)分包,后端接收后直接进批次处理队列,不拼接再拆。
控制每批次大小(不是越大越好)
常见误区是设成 5000 或 10000 条——实际要结合具体环境压测确定:
- MySQL 默认
max_allowed_packet通常为 4MB,单条记录若平均 2KB,1000 条 ≈ 2MB,安全冗余;超过易报错Packet for query is too large。 - MyBatis 批量插入(
batchInsert)底层仍是多条INSERT INTO ... VALUES (),(),(),语句过长会触发 MySQL 的group_concat_max_len或解析瓶颈。 - 实测建议起始值:1000 条/批(兼顾网络吞吐与事务粒度),上线后根据监控(执行耗时、GC 频率、DB CPU)动态调整至 2000–3000。
用事务 + 批次提交降低锁与回滚开销
千万级导入最怕长事务:锁表时间长、undo log 膨胀、主从延迟飙升。
- 每个批次单独开启事务,插入完立刻
commit,失败则仅回滚当前批,不影响后续。 - 禁用“全量包在一个 @Transactional 中”——这是高危写法,哪怕加了
timeout也救不了锁竞争。 - MyBatis-Plus 的
saveBatch(list, batchSize)默认不带事务控制,需自行包装在TransactionTemplate或@Transactional(propagation = Propagation.REQUIRES_NEW)方法中调用。
必要时引入异步与生产者-消费者解耦
当读取(如解析 Excel)和写入(JDBC 插入)速度不匹配时,同步阻塞会拖垮整体吞吐。
- 用
BlockingQueue(如ArrayBlockingQueue)做缓冲区,Reader 线程生产,Writer 线程消费,双方速率解耦。 - 搭配线程池(如
ThreadPoolTaskExecutor),核心线程数建议设为 DB 连接池最大值(如 HikariCP 的maximumPoolSize=20),避免连接争抢。 - 注意队列容量设限(如 10000),防内存失控;满时 Reader 可阻塞或降速,而非无脑丢弃。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











