批量更新防内存溢出的核心是分批处理,每批500–2000条,采用流式查询、手动事务控制和preparedstatement批量api,避免全量加载与对象长期驻留。

批量更新数据库时防止内存溢出,核心是控制单次加载和处理的数据量,避免把全部数据一次性塞进 JVM 堆中。不是靠堆内存调大来硬扛,而是从数据流、对象生命周期和执行方式三方面做节制。
分批处理,明确每批次大小
不把 100 万条数据全读进一个 List 再切分。应边查边处理,或至少按固定大小(如 500–2000 条)严格分片:
- 使用
SELECT ... LIMIT offset, size或数据库原生分页(如 MySQL 的LIMIT、Oracle 的ROWNUM、PostgreSQL 的OFFSET/LIMIT)逐批拉取 - Java 层用
subList()切分前务必确认原集合已释放或不长期持有——比如查询后立即处理,不缓存全量结果 - 推荐每批 500–2000 条:太小增加事务和网络开销;太大易触发 OOM(尤其字段多、含 BLOB 时)
避免全量加载 + 集合长期驻留
常见陷阱是先 queryForList() 拿出全部数据,再 for 循环分批——这等于把所有数据先占满堆内存:
- 改用流式查询(如 MyBatis 的
Cursor、JDBC 的Statement.setFetchSize(Integer.MIN_VALUE)),让数据库逐批返回,JVM 不囤积 - 静态或单例 Bean 中不要缓存大批量待更新数据;方法内局部变量处理完即丢弃,不赋值给成员变量或 static 集合
- 更新完一批后,显式将
batchData置为null或脱离作用域,助 GC 快速回收
慎用多线程,警惕资源叠加
并发分批看似快,但若没控好,反而放大内存压力:
- 线程池不宜过大(如核心线程数 ≤ CPU 核数 × 2),避免多个线程同时加载各自批次,堆内存被多份副本挤爆
- 每个线程内仍需分批,不能“一个线程扛 10 万条”
- 优先考虑异步+队列模式:主线程分片后发任务到阻塞队列,工作线程逐个消费,天然限流
数据库侧配合减压
内存问题常是数据库交互方式引发的连锁反应:
- 关闭自动提交,手动控制事务边界——每批一个事务,不把百万条包在一个大事务里(否则数据库 undo log 和连接内存也暴涨)
- 使用 PreparedStatement 批量 API(如
addBatch()+executeBatch()),而非拼接 N 条 SQL 字符串 - 确认 JDBC 驱动支持流式结果集(如 MySQL Connector/J 加
useCursorFetch=true&defaultFetchSize=100)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











