
MyBatis批量插入性能优化实战从20分钟到6秒的蜕变之路那天下午服务器监控突然发出刺耳的警报声——一个本该在几分钟内完成的批量数据导入任务已经持续运行了超过20分钟。作为团队的技术负责人我立即放下手中的咖啡开始了一场与时间赛跑的性能优化之旅。1. 问题定位为什么批量插入如此缓慢当我打开日志系统时眼前的数字令人震惊12,000条记录每条包含25个字段总耗时23分41秒。更奇怪的是服务器资源监控显示CPU和内存使用率都处于低位完全没有达到瓶颈状态。通过JDBC日志分析发现了几个关键问题点SimpleExecutor的局限性默认的SimpleExecutor为每条SQL创建独立的PreparedStatement未启用的批处理特性MySQL驱动参数rewriteBatchedStatements未设置为true巨型SQL语句使用foreach生成的单条SQL长度超过1MB// 问题代码示例原始实现 public int batchInsert(ListDataRecord records) { return sqlSession.insert(com.example.mapper.DataMapper.batchInsert, records); }对应的Mapper XML配置insert idbatchInsert parameterTypejava.util.List INSERT INTO data_table ( field1, field2, ..., field25 ) VALUES foreach collectionlist itemitem separator, (#{item.field1}, #{item.field2}, ..., #{item.field25}) /foreach /insert2. 优化策略多管齐下的性能提升方案2.1 JDBC驱动层优化MySQL的JDBC驱动有个不为人知的黑魔法参数rewriteBatchedStatements它能让批量插入产生质的飞跃# application.properties关键配置 spring.datasource.urljdbc:mysql://localhost:3306/db?rewriteBatchedStatementstrueuseServerPrepStmtsfalse注意rewriteBatchedStatementstrue会将多条INSERT语句重写为单条多值语法而useServerPrepStmtsfalse可以避免服务端预处理语句的开销2.2 ExecutorType的正确使用MyBatis提供了三种执行器类型我们需要根据场景灵活选择执行器类型特点适用场景SIMPLE (默认)每条语句单独执行常规CRUD操作REUSE复用预处理语句同语句多次执行BATCH批量执行优化大批量数据操作优化后的代码实现public int optimizedBatchInsert(ListDataRecord records) { SqlSession batchSession sqlSessionFactory.openSession(ExecutorType.BATCH); try { DataMapper mapper batchSession.getMapper(DataMapper.class); int count 0; for (DataRecord record : records) { mapper.insertSingle(record); if (count % 500 0) { batchSession.flushStatements(); } } batchSession.commit(); return count; } finally { batchSession.close(); } }2.3 分批处理的艺术通过测试发现当单批次数据量在100-500条时性能达到最佳平衡点。我们实现了智能分批策略// 智能分批处理实现 public void smartBatchInsert(ListDataRecord allRecords) { int batchSize 300; // 经过测试的最佳值 ListListDataRecord batches ListUtils.partition(allRecords, batchSize); batches.forEach(batch - { SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH); try { DataMapper mapper session.getMapper(DataMapper.class); batch.forEach(mapper::insertSingle); session.commit(); } finally { session.close(); } }); }3. 进阶优化MyBatis-Plus的批量操作对于使用MyBatis-Plus的项目其内置的批量操作方法可以进一步简化代码Service public class DataServiceImpl extends ServiceImplDataMapper, DataRecord { Transactional public void batchInsertWithPlus(ListDataRecord records) { saveBatch(records, 300); // 使用MP的批量保存方法 } }MyBatis-Plus的批量操作内部实现原理自动使用BatchExecutor内置分批处理逻辑自动管理会话和事务4. 性能对比与最佳实践经过上述优化后我们进行了全面的性能测试优化阶段12,000条记录耗时性能提升原始foreach方式23m41s基准仅添加rewrite参数8m12s65%↑BATCH执行器1m45s92%↑分批处理(300条/批)32s98%↑最终优化版本(综合方案)6s99.6%↑基于实战经验总结出以下最佳实践连接参数必须包含rewriteBatchedStatementstrueuseServerPrepStmtsfalse事务控制要点批量操作要在单个事务中完成避免在批量操作中混入查询语句内存管理技巧定期调用flushStatements()释放内存考虑使用fetchSize参数控制内存占用异常处理策略批量操作失败时要完整回滚记录详细的错误日志以便排查// 完整的批量插入工具类实现 public class BatchInsertHelper { private static final int DEFAULT_BATCH_SIZE 300; public static T int execute(SqlSessionFactory sessionFactory, Class? extends BaseMapperT mapperClass, ListT dataList, ConsumerBaseMapperT operation) { int total 0; SqlSession session sessionFactory.openSession(ExecutorType.BATCH); try { BaseMapperT mapper session.getMapper(mapperClass); for (int i 0; i dataList.size(); i) { operation.accept(mapper); if (i 0 i % DEFAULT_BATCH_SIZE 0) { session.flushStatements(); } total; } session.commit(); } catch (Exception e) { session.rollback(); throw new RuntimeException(Batch insert failed, e); } finally { session.close(); } return total; } }经过这次优化实战我们不仅解决了眼前的性能问题更重要的是建立了一套完整的批量操作规范。在后续的支付对账、日志归档等场景中这套方案都表现出了优异的性能。当看到同样的数据量从20分钟缩短到6秒时团队每个成员脸上都露出了欣慰的笑容——这就是技术优化的魅力所在。