1. 问题背景:MyBatis批量插入的性能瓶颈
最近在优化一个数据迁移模块时,遇到了一个典型性能问题:使用MyBatis批量插入10万条记录需要近5分钟。这种性能在实时性要求高的场景下是完全不可接受的。经过系统排查,发现主要存在以下三个性能瓶颈:
- JDBC连接未启用批量模式(默认逐条提交) 2.事务范围过大导致内存溢出风险
- 单线程处理无法充分利用多核CPU优势
注意:MyBatis默认的批量操作实际上是伪批量,底层仍是循环单条执行。需要特殊配置才能实现真正的批量提交。
2. 关键优化方案与实施步骤
2.1 启用真正的JDBC批量模式
在MyBatis配置中增加以下参数:
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id" parameterType="java.util.List"> <!-- SQL语句 --> </insert>同时在JDBC连接字符串中添加关键参数:
rewriteBatchedStatements=true allowMultiQueries=true useServerPrepStmts=false原理说明:
rewriteBatchedStatements:MySQL驱动会将多个INSERT语句重写为单个多值INSERTallowMultiQueries:允许一次执行多条SQL语句useServerPrepStmts:禁用服务器端预处理(批量场景下反而降低性能)
2.2 合理控制事务范围
将大事务拆分为小批次提交:
// 每5000条提交一次 int batchSize = 5000; for (int i = 0; i < total; i += batchSize) { List<Record> subList = list.subList(i, Math.min(i + batchSize, total)); sqlSessionTemplate.insert("batchInsert", subList); sqlSessionTemplate.flushStatements(); }注意事项:
- 批次大小需要根据数据行大小和内存情况调整
- 过大的批次会导致内存溢出,过小则影响性能
- 建议在测试环境进行压力测试确定最佳批次大小
2.3 引入多线程并行处理
使用线程池实现并行插入:
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); List<Future<Integer>> futures = new ArrayList<>(); int threadCount = 10; // 线程数 int perThreadSize = total / threadCount; for (int i = 0; i < threadCount; i++) { int start = i * perThreadSize; int end = (i == threadCount - 1) ? total : start + perThreadSize; futures.add(executor.submit(() -> { return batchInsert(list.subList(start, end)); })); } // 等待所有线程完成 for (Future<Integer> future : futures) { future.get(); }3. 优化效果对比
| 优化措施 | 10万条耗时 | 性能提升 |
|---|---|---|
| 原始方案 | 5分12秒 | - |
| 仅JDBC批量 | 1分45秒 | 3倍 |
| 批量+分事务 | 38秒 | 8倍 |
| 全优化方案 | 2.8秒 | 111倍 |
4. 常见问题与解决方案
4.1 主键冲突问题
现象:多线程插入时出现主键重复
解决方案:
- 使用数据库序列或UUID生成主键
- 采用Snowflake等分布式ID生成器
- 设置
useGeneratedKeys=false手动分配ID
4.2 连接池耗尽
现象:报错"Could not get JDBC Connection"
优化方案:
- 增加连接池大小:
spring.datasource.hikari.maximum-pool-size=20- 设置合理的等待超时:
spring.datasource.hikari.connection-timeout=300004.3 内存溢出风险
预防措施:
- 限制单批次处理量(建议不超过1万条)
- 及时清理MyBatis缓存:
sqlSession.clearCache();- 增加JVM堆内存:
-Xmx2g -Xms2g5. 进阶优化技巧
5.1 使用MyBatis-Plus的saveBatch
List<User> userList = ...; userService.saveBatch(userList, 5000); // 每5000条分批提交5.2 调整MySQL服务器参数
-- 增大排序缓冲区 SET GLOBAL sort_buffer_size = 64*1024*1024; -- 临时关闭binlog SET sql_log_bin = 0;5.3 监控与调优工具
- 使用Arthas监控JDBC执行:
watch com.mysql.jdbc.JDBC4Connection * '{params,returnObj}' -x 3- 分析慢查询日志:
slow_query_log=1 long_query_time=1在实际项目中,建议先进行小规模测试,逐步应用这些优化措施。不同数据规模、硬件环境下最佳参数可能有所差异,需要根据实际情况调整。