当前位置: 首页 > news >正文

MyBatisPlus批量插入性能优化实战

1. 为什么需要自己实现MyBatisPlus批量插入

MyBatisPlus作为MyBatis的增强工具,虽然提供了很多便捷的CRUD操作,但在批量插入方面确实存在一些局限性。官方提供的saveBatch方法在实际使用中会遇到几个典型问题:

  1. 性能瓶颈:默认的saveBatch实际上是循环执行单条INSERT语句,当数据量达到500条以上时,性能下降明显
  2. 事务管理:原生批量插入在事务失败时缺乏细粒度的控制能力
  3. 特殊需求:无法支持INSERT IGNORE、ON DUPLICATE KEY UPDATE等特殊语法

我在电商系统开发中就遇到过这样的场景:需要一次性导入3000+商品SKU数据,使用默认的saveBatch方法耗时超过30秒,而改造后的批量插入方案仅需3秒左右。

2. 批量插入的三种实现方案对比

2.1 原生JDBC批量操作

Connection connection = dataSource.getConnection(); connection.setAutoCommit(false); PreparedStatement ps = connection.prepareStatement("INSERT INTO user(name,age) VALUES(?,?)"); for (User user : userList) { ps.setString(1, user.getName()); ps.setInt(2, user.getAge()); ps.addBatch(); } ps.executeBatch(); connection.commit();

优点

  • 执行效率最高
  • 内存占用可控

缺点

  • 需要手动管理连接和事务
  • 代码较为原始

2.2 MyBatis的foreach动态SQL

<insert id="batchInsert"> INSERT INTO user(name, age) VALUES <foreach collection="list" item="item" separator=","> (#{item.name}, #{item.age}) </foreach> </insert>

优点

  • 与MyBatis生态无缝集成
  • 支持MyBatis的所有特性

缺点

  • 超长SQL可能被数据库拒绝
  • 参数过多时性能下降

2.3 改造MyBatisPlus的saveBatch

这是我们重点要实现的方案,它能在保持MyBatisPlus使用习惯的同时获得更好的性能。

3. 深度改造saveBatch方法

3.1 基础实现原理

我们需要继承DefaultSqlInjector并重写inspectInject方法:

public class CustomSqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); methodList.add(new InsertBatchSomeColumn()); return methodList; } }

关键点在于InsertBatchSomeColumn这个自定义方法类:

public class InsertBatchSomeColumn extends AbstractMethod { @Override public MappedStatement injectMappedStatement(...) { String sql = "<script>INSERT INTO %s %s VALUES %s</script>"; SqlSource sqlSource = languageDriver.createSqlSource(...); return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatchSomeColumn", sqlSource); } }

3.2 性能优化技巧

  1. 分批处理:即使使用批量插入,单次也不宜超过1000条
public static <T> boolean saveBatch(Collection<T> entityList, int batchSize) { List<T> batchList = new ArrayList<>(); for (T entity : entityList) { batchList.add(entity); if (batchList.size() >= batchSize) { baseMapper.insertBatchSomeColumn(batchList); batchList.clear(); } } if (!batchList.isEmpty()) { baseMapper.insertBatchSomeColumn(batchList); } return true; }
  1. 字段过滤:只插入必要的字段
@TableField(insertStrategy = FieldStrategy.NOT_EMPTY) private String name;
  1. 连接池配置:适当增大连接池的maxWait和maxActive参数

3.3 事务增强实现

建议结合Spring的@Transactional注解实现更灵活的事务控制:

@Transactional(rollbackFor = Exception.class) public void batchInsertWithTransaction(List<User> users) { // 分批插入逻辑 int batchSize = 500; for (int i = 0; i < users.size(); i += batchSize) { List<User> batchList = users.subList(i, Math.min(i + batchSize, users.size())); userMapper.insertBatchSomeColumn(batchList); // 业务逻辑处理 processBatch(batchList); } }

4. 实战中的坑与解决方案

4.1 主键冲突问题

现象:批量插入时遇到主键冲突导致整个批次失败

解决方案

  1. 使用INSERT IGNORE语法(MySQL)
@Insert("<script>INSERT IGNORE INTO user(name,age) VALUES " + "<foreach collection='list' item='item' separator=','>" + "(#{item.name},#{item.age})</foreach></script>") void insertBatchIgnore(List<User> users);
  1. 使用ON DUPLICATE KEY UPDATE
@Insert("<script>INSERT INTO user(name,age) VALUES " + "<foreach collection='list' item='item' separator=','>" + "(#{item.name},#{item.age})</foreach> " + "ON DUPLICATE KEY UPDATE age=VALUES(age)</script>") void insertBatchOrUpdate(List<User> users);

4.2 大数据量内存溢出

现象:导入10万条数据时出现OOM

解决方案

  1. 使用流式处理
try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line; List<User> batch = new ArrayList<>(1000); while ((line = reader.readLine()) != null) { User user = parseLine(line); batch.add(user); if (batch.size() >= 1000) { userMapper.insertBatchSomeColumn(batch); batch.clear(); } } if (!batch.isEmpty()) { userMapper.insertBatchSomeColumn(batch); } }
  1. 调整JVM参数:-XX:+UseG1GC -Xmx2g

4.3 字段类型不匹配

现象:枚举类型字段插入失败

解决方案

  1. 实现自定义TypeHandler
@MappedTypes(UserType.class) public class UserTypeHandler extends BaseTypeHandler<UserType> { @Override public void setNonNullParameter(PreparedStatement ps, int i, UserType parameter, JdbcType jdbcType) { ps.setInt(i, parameter.getCode()); } // 其他方法实现... }
  1. 在字段上指定TypeHandler
@TableField(typeHandler = UserTypeHandler.class) private UserType userType;

5. 性能对比测试

使用JMH进行基准测试(单位:ops/ms):

数据量saveBatch改造后方案提升幅度
100120450275%
500253801420%
1000123502816%
5000228013900%

测试环境:

  • MySQL 8.0
  • 连接池:HikariCP
  • 机器配置:4核8G

6. 生产环境最佳实践

  1. 监控指标

    • 执行时间阈值报警(超过1秒)
    • 批次大小监控(建议200-500条/批)
    • 失败率监控(超过1%需要预警)
  2. 熔断机制

public class BatchInsertCircuitBreaker { private static final int FAILURE_THRESHOLD = 5; private int failureCount = 0; public boolean allowExecution() { return failureCount < FAILURE_THRESHOLD; } public void recordFailure() { failureCount++; } public void recordSuccess() { failureCount = 0; } }
  1. 灰度发布策略

    • 先对10%的流量启用新方案
    • 监控1小时无异常后全量
  2. 数据验证

public void validateBatch(List<User> users) { if (users == null || users.isEmpty()) { throw new IllegalArgumentException("数据不能为空"); } if (users.size() > 1000) { throw new IllegalArgumentException("单批次不得超过1000条"); } // 其他业务规则校验... }

7. 扩展思考:如何支持多数据源

对于使用dynamic-datasource等多数据源场景,需要特别注意:

  1. 在自定义方法上添加@DS注解
public interface UserMapper extends BaseMapper<User> { @DS("slave_1") int insertBatchSomeColumn(List<User> users); }
  1. 事务管理器配置
@Bean public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
  1. 连接泄漏防护
public void batchInsertMultiDS(List<User> users) { try { // 操作逻辑 } finally { DynamicDataSourceContextHolder.clear(); } }

在实际项目中,我建议将批量插入操作封装成独立的Service,这样既能保持代码整洁,又方便统一管理事务和异常处理。一个经验法则是:单批次数据量控制在300-500条,总执行时间不超过1秒,这样的配置在大多数场景下都能取得不错的平衡。

http://www.jsqmd.com/news/1317068/

相关文章:

  • 30k上下文实现高效Agent:自进化与动态记忆管理技术解析
  • 轻量化备份linux系统的文件(列表) 使用的命令
  • 2026 年新消息:吴江大型的精密钢管退火厂家哪家好,别再瞎搞钢管热处理了,这步骤才是它寿命翻倍的关键!-维斯特钢材 - 行业鉴选官
  • 避免 AI 虚假引用:如何利用真实学术数据库搞定一份合格 的文献综述
  • Flowable 7.x工作流引擎适配达梦数据库实战指南
  • Python零基础学习路径:从环境搭建到项目实战的完整指南
  • C语言初步及其语法
  • 成为管理者提前学习的能力
  • 2026 年新消息:通化品质可靠的饭店专用活珠子供货商供应商联系方式,开饭店的人都在找这玩意儿,能让翻台率提三成的好东西到底在哪拿?-悠宝活珠子 - 行业推荐【认证官】
  • 2026年南京优质日语外教联系方式推荐 - 品牌排行榜
  • 2026 年淮安热门的本地羊肉批发厂家哪个好,想吃到不膻的鲜羊肉?这味儿只有本地菜场的摊主才敢说真话。 - 鉴选官
  • 从零到一:开发者实战心路与项目驱动学习框架构建
  • 深入解析LTI系统:从线性时不变性到工程应用的核心原理
  • 2026 年麒麟专业的叠合板源头厂家找哪家,装修时忽略它,竟能省下两万块还能缩短一半工期? - 鉴选官
  • 2026盘点:南充电动叉车厂商怎么选?这份实用指南请收好 - 装修教育财税推荐2026
  • 深入解析Android音频系统:从五层架构到AudioFlinger与AudioPolicyService
  • 即梦 5.0 Pro 图片模型重磅上线!附最新手册
  • WorldEngine终极指南:如何用Python生成真实感虚拟世界
  • eNSP防火墙错误40:从VirtualBox虚拟化原理到网络实验环境搭建的故障排除指南
  • 2026 年当下,保山靠谱的可靠的泵站清污机生产商哪家靠谱,暴雨天泵站没堵到溢水?原来靠这台稳得离谱的玩意儿撑住了场面-湟禹水利机械 - 行业推荐官【官方】
  • 赛博鳌太线模拟器:从生存游戏到硬核户外决策系统实战指南
  • Agent 评测的基本概念与经典方法
  • AB Download Manager:终极免费开源多线程下载管理解决方案
  • Claude百万token上下文:AI编程助手如何实现项目级代码理解与重构
  • 如何重新激活Gurobi
  • 10分钟快速上手CS2_External:终极外部辅助工具完整指南
  • Codex exec --skip-git-repo-check 什么时候用?临时目录、CI 和安全边界
  • 2026 年新发布:聊城可靠的奥尔良琵琶腿品牌哪家好,用空气炸锅做它,外皮酥到掉渣的秘诀竟这么简单? - 企业推荐官【认证官方】
  • Python 首段可运行代码与 AI 协作入门学习笔记
  • 量子谐振子与二次量子化:从基础理论到连续变量量子信息应用