1. 项目概述
最近在开发一个电商后台系统时,遇到了一个性能瓶颈:需要处理每天数十万条订单数据的批量插入。在使用Spring Boot + MyBatis-Plus的组合时,我发现批量插入的性能表现差异巨大,从每秒几十条到上万条都有可能。这让我意识到,批量插入这个看似简单的操作,实际上暗藏玄机。
2. 核心需求解析
2.1 为什么需要批量插入
在数据处理场景中,批量插入相比单条插入能带来显著的性能提升。以订单系统为例:
- 减少网络开销:单条插入需要多次网络往返
- 降低事务开销:批量操作可以合并为一个事务
- 提高数据库效率:数据库引擎可以优化批量操作
2.2 技术选型考量
选择Spring Boot + MyBatis-Plus的组合主要基于:
- Spring Boot的自动配置简化了项目搭建
- MyBatis-Plus在MyBatis基础上提供了更便捷的CRUD操作
- 两者组合是国内Java开发的主流选择
3. 实现方案对比
3.1 原生JDBC批量插入
String sql = "INSERT INTO orders(...) VALUES(?,?,...)"; try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { conn.setAutoCommit(false); for (Order order : orders) { ps.setString(1, order.getOrderNo()); // 设置其他参数... ps.addBatch(); if (i % batchSize == 0) { ps.executeBatch(); } } ps.executeBatch(); conn.commit(); }优点:
- 性能最好
- 控制粒度最细
缺点:
- 代码冗长
- 需要手动处理事务
3.2 MyBatis XML方式批量插入
<insert id="batchInsert" parameterType="java.util.List"> INSERT INTO orders(...) VALUES <foreach collection="list" item="item" separator=","> (#{item.orderNo},...) </foreach> </insert>优点:
- 代码简洁
- 可读性好
缺点:
- SQL长度可能超限
- 事务管理仍需注意
3.3 MyBatis-Plus的saveBatch方法
orderService.saveBatch(orders, batchSize);优点:
- 使用最简单
- 内置批处理逻辑
缺点:
- 性能不如前两种
- 黑盒操作,可控性差
4. 性能实测数据
在本地环境(MySQL 8.0,10000条数据)测试结果:
| 方案 | 耗时(ms) | 内存占用(MB) |
|---|---|---|
| 单条插入 | 12500 | 150 |
| JDBC批量 | 320 | 50 |
| MyBatis XML | 450 | 60 |
| MP saveBatch | 800 | 70 |
5. 实战中的坑与解决方案
5.1 MySQL配置优化
# application.properties spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 rewriteBatchedStatements=true关键参数说明:
- rewriteBatchedStatements:启用批量语句重写
- 连接池大小需要合理设置
5.2 事务管理要点
@Transactional public void batchInsert(List<Order> orders) { // 分批处理 Lists.partition(orders, 1000).forEach(batch -> { orderMapper.batchInsert(batch); }); }注意事项:
- 避免一个大事务包含太多数据
- 考虑使用编程式事务
5.3 内存溢出预防
处理大数据量时:
- 使用流式查询读取数据
- 分批处理,及时清空缓存
- 监控JVM内存使用
6. 高级优化技巧
6.1 多线程批量插入
ExecutorService executor = Executors.newFixedThreadPool(4); Lists.partition(orders, 2500).forEach(batch -> { executor.submit(() -> { orderService.saveBatch(batch); }); }); executor.shutdown();注意点:
- 线程池大小根据数据库连接池调整
- 需要考虑数据顺序性要求
6.2 使用LOAD DATA INFILE
对于超大数据量:
String loadDataSql = "LOAD DATA LOCAL INFILE 'temp.csv' INTO TABLE orders..."; jdbcTemplate.execute(loadDataSql);优势:
- MySQL原生批量加载
- 性能最优
限制:
- 需要文件中间件
- 权限配置复杂
7. 监控与调优
7.1 监控指标
关键监控点:
- 批量插入耗时
- 数据库CPU/IO使用率
- JVM内存变化
- 慢查询日志
7.2 性能分析工具
推荐工具:
- Arthas:分析Java方法耗时
- VisualVM:监控JVM状态
- MySQL EXPLAIN:分析SQL执行计划
8. 最佳实践总结
经过多次实战验证,我的建议是:
- 中小数据量(<10万):MyBatis-Plus saveBatch + 合理批大小
- 大数据量(10万+):JDBC批量 + 多线程
- 超大数据量(百万+):考虑LOAD DATA INFILE
关键参数设置:
- 批大小:500-2000为宜
- 事务大小:每事务不超过5000条
- 线程数:不超过连接池大小
最后提醒:任何批量操作都要考虑失败回滚方案,做好数据校验和日志记录。在实际项目中,我通常会先在小批量数据上测试,确认无误后再全量执行。