Spring Boot 批量操作实战:别再一条一条处理数据
欢迎阅读我的文章!更多精彩内容,欢迎关注:
• B站主页:小枫Geek
• 微信公众号:Procode
在真实项目中,批量数据通常出现在几个场景:
-
批量导入数据
-
批量更新状态
-
批量同步第三方数据
-
批量导出报表
-
定时任务处理历史数据
很多系统一开始写法非常简单:
for(User user : userList){ userMapper.insert(user);}
代码看起来干净,但这在数据量稍大时就会出现两个问题:
-
SQL 请求次数过多
-
事务和连接资源被大量占用
当数据达到几万条时,这种写法几乎一定会拖慢系统。
所以真正优雅的处理方式,需要从几个层面优化。
一、第一原则:避免“逐条操作数据库”
数据库最讨厌的一件事就是:高频小请求。
如果 10000 条数据逐条 insert,就意味着:
-
10000 次 SQL
-
10000 次网络通信
-
10000 次数据库解析
正确思路是:尽量减少 SQL 次数。
例如使用批量插入:
INSERT INTO user(name,age)VALUES('A',20),('B',21),('C',22)
一次 SQL 解决几十甚至几百条数据。
在 MyBatis 中,可以使用 foreach 实现:
<insert id="batchInsert"> INSERT INTO user(name,age) VALUES <foreach collection="list" item="item" separator=","> (#{item.name}, #{item.age}) </foreach></insert>
这种方式会大幅减少数据库压力。
二、第二原则:控制批量大小
很多人意识到要批量插入之后,就会走向另一个极端:一次插入几万条数据。
这同样危险。
原因很简单:
-
SQL 太长
-
数据库解析压力大
-
内存占用高
现实项目中比较常见的批量大小是:
-
100
-
500
-
1000
例如:
int batchSize = 500;for (int i = 0; i < list.size(); i += batchSize) { int end = Math.min(i + batchSize, list.size()); List<User> batch = list.subList(i, end); userMapper.batchInsert(batch);}
这样可以在效率和稳定之间取得平衡。
三、事务不要包住整个批量任务
很多开发者会写这样的代码:
@Transactionalpublic void importData(List<User> list){ for(User user : list){ userMapper.insert(user); }}
如果数据量很大,这个事务会持续很久。
问题包括:
-
长事务锁表
-
数据库连接被长期占用
-
一旦失败全部回滚
更合理的方式是:分批提交事务。
例如:每 500 条提交一次。
这样即使任务失败,也只影响当前批次。
四、避免一次性加载大量数据
另一个常见问题是:一次性把几十万数据加载到内存。
例如:
List<User> users = userMapper.selectList(null);
如果表有 50 万条数据,JVM 很容易出现内存压力。
更好的方式是:分页处理。
例如:
int pageSize = 1000;int page = 1;while(true){ List<User> users = userMapper.selectPage(page,pageSize); if(users.isEmpty()){ break; } process(users); page++;}
这种方式可以保证:
-
内存稳定
-
任务可控
五、批量任务尽量使用异步处理
有些批量任务本身不需要实时结果,例如:
-
数据导入
-
报表生成
-
数据同步
如果直接在接口里执行,很容易导致接口超时。
Spring Boot 提供了非常简单的异步方案:
@Asyncpublic void batchProcess(){ // 批量任务}
用户提交任务之后,后台慢慢执行即可。
更成熟的系统甚至会:
-
使用消息队列
-
使用任务调度
-
使用任务中心
把批量处理完全从接口层解耦。
六、批量更新要警惕锁表
批量更新比批量插入更危险。
例如:
UPDATE order SET status = 1 WHERE status = 0
如果数据量巨大,很可能造成:
-
长时间锁表
-
其他请求被阻塞
解决思路通常是:分段更新。
例如:
按 ID 范围更新:
UPDATE order SET status = 1WHERE id BETWEEN 1 AND 1000
然后循环执行。
七、必要时使用数据库原生能力
有些批量任务如果完全交给 Java 处理,效率会很低。
例如:
-
批量统计
-
批量数据迁移
-
批量计算
很多时候直接使用 SQL 会更快,例如:
INSERT INTO order_historySELECT * FROM orderWHERE create_time < '2023-01-01'
数据库在处理大数据量时往往比应用层更高效。
八、真正成熟的批量处理架构
在大型系统中,批量处理通常会形成一套完整方案:
-
分批处理
-
异步任务
-
任务状态记录
-
失败重试
-
进度追踪
例如:
用户上传 Excel → 系统创建任务 → 后台异步处理 → 更新任务进度 → 最终生成结果。
这样系统既稳定,又不会影响正常接口。
小结
Spring Boot 处理批量数据时,最重要的不是某个框架技巧,而是三个原则:
-
减少 SQL 次数
-
控制批量规模
-
避免长事务和大内存
只要做到这三点,大多数批量数据问题都能解决。很多系统性能问题,本质上不是架构复杂,而是:把小数据的写法,直接用在大数据场景。
更多推荐

所有评论(0)