> 技术文档 > MyBatis 批量插入性能优化全解析:为什么不要用 foreach 处理大数据量插入?

MyBatis 批量插入性能优化全解析:为什么不要用 foreach 处理大数据量插入?

在日常项目中,我们常常会遇到批量插入数据的场景,例如导入 Excel 文件中的数据、同步第三方接口返回的数据列表,或者定时任务执行数据写入等。很多开发者下意识地会选择在 MyBatis 中使用 标签遍历集合,并执行多条 INSERT 语句,甚至尝试通过一条 INSERT 语句拼接多个 VALUES 来实现批量插入。

但如果你在生产环境中试过大数据量(比如上百条、上千条)插入后明显变慢,甚至严重影响了系统响应,那你绝对不能错过这篇文章。我们将从底层原理和实际表现两个层面,深入剖析为什么不推荐用 来进行大批量插入,并介绍正确的优化方式:MyBatis 批处理(Batch Insert)机制


一、常见的错误用法:使用 插入大数据量

许多开发者会采用以下方式实现批量插入:

<insert id=\"insertBatch\"> insert into user (id, name) values <foreach collection=\"list\" item=\"item\" separator=\",\"> (#{item.id}, #{item.name}) </foreach></insert>

在小数据量(几十条)场景下,这种方式表现尚可。但如果遇到 Excel 导入、日志归档等场景,一次插入上百甚至上千条记录时,性能问题便暴露无遗:执行时间成倍增长、数据库压力陡升,严重时甚至超时失败


二、性能瓶颈根源解析:SQL 参数占位符过多导致解析开销激增

上述 方式看似实现了多值拼接的效果,但背后实际上是 MyBatis 利用 JDBC 的 PreparedStatement 机制,为每条记录都生成一组参数占位符(即 ?):

举例来说,若每条记录包含两个字段(如 idname),那么插入 1000 条数据时就会生成 2000 个问号参数

insert into user (id, name) values (?, ?), (?, ?), ..., (?, ?)-- 共1000组 (?, ?)

MyBatis 底层为了处理这些参数,会经历以下流程:

  1. 构建 SQL 文本(含 2000 个 ? 占位符);
  2. JDBC 层解析 SQL 并绑定所有参数值
  3. 数据库执行 SQL 并生成执行计划

当参数个数急剧增加时,SQL 解析与参数绑定的性能开销会呈指数级增长,导致执行效率直线下降。甚至可能出现:单条拼接型 SQL 执行还不如循环单条插入的情况。


三、真实性能对比:问号数量 vs 执行时间

以下为一条含不同数据量的 PreparedStatement 执行效率分析:

数据条数 占位符数 执行速度趋势 10条 20个 较慢但可接受 50条 100个 开始提速 100条 200个 达到峰值 >100条 >200个 性能直线下降

当数据量持续增加(如 500 条对应 1000 个问号,1000 条对应 2000 个问号)时,执行效率大幅下降,这正是程序运行缓慢的根源


四、正确姿势:使用 MyBatis 批量插入(Batch Insert)

MyBatis 实际上支持真正的批量操作,通过以下方式可实现更高效的插入:

1. 开启批处理执行器

在配置 SqlSessionFactory 时使用批处理模式:

SqlSessionFactory sqlSessionFactory = ...;SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);

其中 ExecutorType.BATCH 告诉 MyBatis 使用批处理执行器,而非默认的简单或重用模式。

2. 编程式批量插入

在 Mapper 中定义普通单条插入方法,然后通过代码控制批次大小,执行如下逻辑:

try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper mapper = session.getMapper(UserMapper.class); int batchSize = 100; for (int i = 0; i < userList.size(); i++) { mapper.insert(userList.get(i)); if (i % batchSize == 0 || i == userList.size() - 1) { session.flushStatements(); // 提交批处理 } } session.commit(); // 提交事务}

3. Spring + MyBatis 配合使用时的注意事项

若项目中使用了 Spring 管理事务与 Mapper,可以通过 @Transactional 搭配 ExecutorType.BATCH 手动构造批量 SqlSession 或使用 MyBatis-Spring-Boot-Starter 的辅助配置,提升兼容性和易用性。


五、底层原理补充:JDBC 批处理机制

MyBatis 的 ExecutorType.BATCH 本质上是基于 JDBC 的批处理能力实现的,核心机制如下:

  • 使用 PreparedStatement.addBatch() 将多个 SQL 操作缓存在内存中;
  • 调用 executeBatch() 一次性发送给数据库;
  • 数据库执行时无需重新解析 SQL,每条语句共用一个解析计划,仅绑定参数。

这意味着:

  • 避免了大量的 SQL 文本拼接与解析;
  • 避免了每次网络往返的消耗;
  • 提高了整体吞吐性能;

在插入上千条记录的情况下,执行效率可提升十倍以上


六、最佳实践总结

插入方式 适用场景 性能表现 是否推荐 多组 VALUES 拼接 少量数据(<50条) 一般 ✔ 有条件使用 单条多次插入 任意数据量 较慢 ❌ 不推荐 MyBatis Batch Insert 大数据量(>100条) 最佳 ✔ 强烈推荐 原生 JDBC 批处理 底层控制或框架外使用 优秀 ✔ 推荐

七、参考建议与扩展

  1. 控制批次大小:建议每批 100~500 条,根据具体业务和 DB 性能调优;
  2. 批处理与事务结合使用:确保数据一致性;
  3. 分页分批导入大文件数据:Excel 导入时要注意分页加载与分页插入结合;
  4. 异步化处理:可结合 MQ 或线程池异步插入数据,避免阻塞主流程;
  5. 避免拼接超长 SQL:超过数据库默认 SQL 长度限制时会直接报错。

结语

MyBatis 是一款优秀的 ORM 框架,但它的默认行为并不总是为性能而优化。面对大数据量写入的场景时,了解其底层机制,合理使用批处理模式,是开发高性能系统的关键一环。

希望今天的内容能帮助你理解并掌握 MyBatis 批量插入的正确姿势,在面对百万级数据写入场景时,依然能保持高性能、稳定可靠的系统运行。