欢迎阅读我的文章!更多精彩内容,欢迎关注:
• B站主页
小枫Geek
• 微信公众号Procode  


在真实项目中,批量数据通常出现在几个场景:

  • 批量导入数据

  • 批量更新状态

  • 批量同步第三方数据

  • 批量导出报表

  • 定时任务处理历史数据

很多系统一开始写法非常简单:

for(User user : userList){    userMapper.insert(user);}

代码看起来干净,但这在数据量稍大时就会出现两个问题:

  1. SQL 请求次数过多

  2. 事务和连接资源被大量占用

当数据达到几万条时,这种写法几乎一定会拖慢系统。

所以真正优雅的处理方式,需要从几个层面优化。

 


一、第一原则:避免“逐条操作数据库”

数据库最讨厌的一件事就是:高频小请求。

如果 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 次数

  • 控制批量规模

  • 避免长事务和大内存

        只要做到这三点,大多数批量数据问题都能解决。很多系统性能问题,本质上不是架构复杂,而是:把小数据的写法,直接用在大数据场景。

 

 

 

 

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐