题目
MyBatis批量插入性能优化与实现方案
信息
- 类型:问答
- 难度:⭐⭐
考点
动态SQL,批量操作,性能优化,执行器原理
快速回答
当使用MyBatis的
- SQL语句过长:拼接的SQL可能超出数据库限制(如MySQL的max_allowed_packet)
- 性能低下:单条超长SQL解析执行效率远低于批量提交
优化方案:
- 使用
ExecutorType.BATCH模式配合sqlSession.flushStatements() - 设置
rewriteBatchedStatements=true(MySQL)启用真正的批量操作 - 分批次提交(建议每1000条提交一次)
问题现象与原理说明
当使用如下动态SQL进行批量插入时:
<insert id="batchInsert">
INSERT INTO users (name, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>随着数据量增大(如10万条),会产生:
- SQL长度问题:拼接后的SQL可能长达几MB,超过数据库配置的
max_allowed_packet - 性能瓶颈:
- 数据库解析巨型SQL效率低下
- 事务锁定时间过长
- 内存占用过高可能导致OOM
优化方案与代码实现
方案1:BatchExecutor批量模式(推荐)
try (SqlSession sqlSession = sqlSessionFactory
.openSession(ExecutorType.BATCH)) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (int i = 0; i < dataList.size(); i++) {
mapper.insertSingle(dataList.get(i));
// 每1000条提交一次
if (i % 1000 == 0 || i == dataList.size() - 1) {
sqlSession.flushStatements();
}
}
sqlSession.commit();
}关键配置:
- MySQL连接串添加
rewriteBatchedStatements=true - MyBatis映射文件使用单条插入语句:
<insert id="insertSingle"> INSERT INTO users(name, email) VALUES(#{name}, #{email}) </insert>
方案2:分批次使用<foreach>
int batchSize = 1000;
for (int i = 0; i < dataList.size(); i += batchSize) {
List<User> subList = dataList.subList(i, Math.min(i + batchSize, dataList.size()));
userMapper.batchInsert(subList); // 使用原动态SQL但每次只传1000条
}最佳实践
- 批处理大小:根据数据库性能调整(通常500-2000条/批)
- 事务控制:整个批量操作应在同一事务中
- 连接池配置:增加最大连接数应对并发批量操作
- 监控指标:关注
BatchExecutor的batch和update调用次数
常见错误
- 忘记提交事务:Batch模式需要手动
commit() - 缺少flushStatements:导致所有语句缓存在内存未执行
- 未启用MySQL批量重写:未配置
rewriteBatchedStatements=true实际仍是单条插入 - 超大事务:未分批次提交导致事务过长
扩展知识
- Executor原理:
SIMPLE:默认模式,每条语句单独执行BATCH:缓存多个PreparedStatement批量提交REUSE:复用预处理语句
- JDBC优化:
addBatch():将语句添加到批处理executeBatch():执行批处理命令
- 性能对比(10万条数据测试):
方式 耗时 内存占用 巨型<foreach> 25s 1.2GB BatchExecutor(无rewrite) 18s 300MB BatchExecutor(有rewrite) 3s 50MB