前言
在使用MyBatis-Plus进行批量数据插入时,许多开发者会发现:即使调用saveBatch
方法,数据库仍会产生大量INSERT语句 。本文将深入源码揭示背后的真相,并提供3种性能优化方案,让你的批量插入速度提升10倍!
一、为什么批量插入这么慢?
1.1 性能测试对比
java
// 测试代码
List<User> users = generateUsers(10000); // 生成1w条测试数据
long start = System.currentTimeMillis();
userService.saveBatch(users);
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
// 测试结果
// 默认配置:耗时 3200ms
// 期待效果:耗时 < 500ms
1.2 SQL监控日志
sql
-- 实际执行的SQL
INSERT INTO user (name,age) VALUES ('user1', 20);
INSERT INTO user (name,age) VALUES ('user2', 21);
...(重复1w次)
二、源码解析:揭开saveBatch的真面目
2.1 核心源码追踪
代码定位 :ServiceImpl.saveBatch
→ SqlHelper.executeBatch
java
// 关键源码片段
public boolean saveBatch(Collection<T> entityList, int batchSize) {
return executeBatch(entityList, batchSize, (sqlSession, entity) ->
sqlSession.insert(sqlStatement, entity)); // 逐条插入
}
2.2 执行流程图示
saveBatch调用 获取SqlSession 循环实体列表 单条插入 提交事务
2.3 慢速根源分析
关键因素 | 影响说明 |
---|---|
SimpleExecutor | 默认执行器逐条提交SQL |
事务提交机制 | 默认自动提交(可优化) |
JDBC网络开销 | 每次插入产生一次网络IO |
三、性能优化方案
3.1 方案一:启用批量执行器(配置优化)
3.1.1 修改配置
yaml
mybatis-plus:
configuration:
default-executor-type: batch # 启用批量模式
global-config:
db-config:
logic-delete-field: isDeleted # 避免逻辑删除干扰
3.1.2 效果验证
sql
-- 批量插入SQL(真实执行)
INSERT INTO user (name, age)
VALUES ('user1',20), ('user2',21)...;
性能提升:1w条数据插入从3200ms → 850ms
3.2 方案二:自定义批量SQL(终极优化)
3.2.1 扩展Mapper接口
java
public interface UserMapper extends BaseMapper<User> {
@Insert("<script>" +
"INSERT INTO user (name, age) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.name}, #{item.age})" +
"</foreach>" +
"</script>")
void insertBatch(@Param("list") List<User> users);
}
3.2.2 服务层调用
java
@Autowired
private UserMapper userMapper;
public void superBatchSave(List<User> users) {
userMapper.insertBatch(users);
}
性能对比:1w条数据插入仅需420ms
3.3 方案三:事务+分批提交(平衡方案)
java
@Transactional
public void batchSave(List<User> users) {
int batchSize = 1000;
for (int i = 0; i < users.size(); i += batchSize) {
List<User> subList = users.subList(i, Math.min(i + batchSize, users.size()));
userService.saveBatch(subList);
}
}
优势 :
✅ 避免大事务导致锁表
✅ 内存占用可控
✅ 兼容默认实现
四、生产环境注意事项
4.1 连接池配置
yaml
spring:
datasource:
hikari:
maximum-pool-size: 20 # 根据并发量调整
connection-timeout: 60000
4.2 监控指标
监控项 | 推荐阈值 | 工具 |
---|---|---|
批量插入耗时 | < 1s/千条 | Grafana + Prometheus |
数据库连接数 | < 80%最大连接数 | Druid监控 |
事务锁等待时间 | < 500ms | SHOW ENGINE INNODB STATUS |
4.3 失败重试机制
java
@Retryable(value = SQLException.class, maxAttempts = 3)
public void batchOperation() {
// 批量操作
}
五、性能对比总结
方案 | 1w条耗时 | 网络请求次数 | 代码侵入性 | 适用场景 |
---|---|---|---|---|
默认saveBatch | 3200ms | 10000 | 无 | 小数据量场景 |
BatchExecutor | 850ms | 1 | 低 | 中大数据量 |
自定义批量SQL | 420ms | 1 | 高 | 极致性能要求 |
事务分批提交 | 1500ms | 10 | 中 | 平衡型方案 |