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
机制,为每条记录都生成一组参数占位符(即 ?
):
举例来说,若每条记录包含两个字段(如 id
和 name
),那么插入 1000 条数据时就会生成 2000 个问号参数:
insert into user (id, name) values (?, ?), (?, ?), ..., (?, ?)-- 共1000组 (?, ?)
MyBatis 底层为了处理这些参数,会经历以下流程:
- 构建 SQL 文本(含 2000 个
?
占位符); - JDBC 层解析 SQL 并绑定所有参数值;
- 数据库执行 SQL 并生成执行计划。
当参数个数急剧增加时,SQL 解析与参数绑定的性能开销会呈指数级增长,导致执行效率直线下降。甚至可能出现:单条拼接型 SQL 执行还不如循环单条插入的情况。
三、真实性能对比:问号数量 vs 执行时间
以下为一条含不同数据量的 PreparedStatement
执行效率分析:
当数据量持续增加(如 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
拼接
单条多次插入七、参考建议与扩展
- 控制批次大小:建议每批 100~500 条,根据具体业务和 DB 性能调优;
- 批处理与事务结合使用:确保数据一致性;
- 分页分批导入大文件数据:Excel 导入时要注意分页加载与分页插入结合;
- 异步化处理:可结合 MQ 或线程池异步插入数据,避免阻塞主流程;
- 避免拼接超长 SQL:超过数据库默认 SQL 长度限制时会直接报错。
结语
MyBatis 是一款优秀的 ORM 框架,但它的默认行为并不总是为性能而优化。面对大数据量写入的场景时,了解其底层机制,合理使用批处理模式,是开发高性能系统的关键一环。
希望今天的内容能帮助你理解并掌握 MyBatis 批量插入的正确姿势,在面对百万级数据写入场景时,依然能保持高性能、稳定可靠的系统运行。