MyBatis-Plus批量插入与多线程性能优化实战 1. 项目背景与核心问题在数据密集型的Java应用中批量数据插入是常见的性能瓶颈场景。以用户行为日志、交易记录、传感器数据等为例单条插入的数据库IO开销会成为系统吞吐量的致命短板。MyBatis-Plus作为MyBatis的增强工具提供了saveBatch批量操作方法但开发者常困惑于其与多线程方案的性能差异。实测场景需要将5000条设备状态记录持久化到MySQL。单条插入耗时约50ms理论总耗时应为250秒——这显然不可接受。本文将对比分析两种优化方案方案A直接使用saveBatch批量插入方案B通过线程池分片执行saveBatch2. 技术方案深度解析2.1 MyBatis-Plus批量保存机制saveBatch底层通过JDBC的rewriteBatchedStatements参数实现真批量操作。当开启该参数时MySQL需在连接字符串添加rewriteBatchedStatementstrue驱动程序会将多个INSERT语句重写为单条多值语法/* 原始语句 */ INSERT INTO device_log VALUES(...); INSERT INTO device_log VALUES(...); /* 重写后 */ INSERT INTO device_log VALUES(...),(...);关键参数说明batchSize控制每批提交的记录数默认1000continueOnError批量失败时是否继续重要提示MySQL的max_allowed_packet参数需调整建议16MB以上否则大批量插入会报包大小超限错误2.2 多线程分片方案设计线程池配置核心参数ThreadPoolExecutor executor new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue(100), // 任务队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() );数据分片策略示例// 将5000条数据分为5个分片每个1000条 ListListDeviceLog shards Lists.partition(dataList, 1000); // 提交分片任务 ListFutureInteger futures new ArrayList(); for (ListDeviceLog shard : shards) { futures.add(executor.submit(() - deviceLogService.saveBatch(shard))); } // 等待所有任务完成 for (FutureInteger future : futures) { future.get(); }3. 性能对比实测测试环境硬件4核CPU/8GB内存数据库MySQL 8.0.26innodb_buffer_pool_size4G网络本地回环测试数据方案1000条5000条10000条单条插入48s241s486ssaveBatch(默认)1.2s3.8s7.5s多线程saveBatch(5线程)0.9s2.1s3.8s关键发现批量操作比单条插入快40倍以上多线程方案在数据量3000条时优势明显线程数并非越多越好测试显示4-6线程为最佳区间4. 生产环境注意事项4.1 事务边界控制多线程方案中每个saveBatch操作应在独立事务中执行。推荐使用Spring的PROPAGATION_REQUIRES_NEW传播级别Transactional(propagation Propagation.REQUIRES_NEW) public void saveBatchInNewTransaction(ListDeviceLog list) { deviceLogMapper.saveBatch(list); }4.2 失败处理机制建议实现以下容错策略记录失败分片信息设置重试机制如使用Spring Retry死信队列存储最终失败记录4.3 线程池调优公式最佳线程数估算线程数 CPU核心数 * (1 平均等待时间/平均计算时间)对于典型的数据库IO操作等待时间约10ms计算时间约2ms4核 * (1 10/2) 24线程但需考虑数据库连接池大小限制如Druid默认maxActive85. 进阶优化方案5.1 混合批处理模式结合JDBC原生批量与多线程// 每个线程使用原生Statement批量 Transactional public void nativeBatchInsert(ListDeviceLog list) { SqlSession session sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH); DeviceLogMapper mapper session.getMapper(DeviceLogMapper.class); list.forEach(mapper::insert); session.commit(); }5.2 动态分片策略根据数据特征自动调整分片大小int dynamicBatchSize Math.max(100, 5000 / Runtime.getRuntime().availableProcessors());5.3 监控指标埋点建议监控批次执行时间分布Prometheus Histogram线程池队列积压情况数据库QPS与连接数6. 典型问题排查6.1 批量插入变慢检查点确认rewriteBatchedStatementstrue已生效检查是否有唯一索引冲突监控数据库锁等待情况SHOW ENGINE INNODB STATUS6.2 多线程数据错乱解决方案使用ThreadLocal存储线程上下文确保实体类无共享状态对公共资源加锁如Redis分布式锁6.3 内存溢出风险预防措施限制单批次数据量建议不超过5000条采用流式分片读取如使用MyBatis的Cursor设置JVM最大堆内存-Xmx为数据量的3倍以上通过以上对比可见对于5000条级别的数据插入多线程saveBatch组合方案比纯批量插入可再提升40%-50%性能。但在实际应用中建议根据具体硬件配置、数据库负载、业务容忍度等因素进行针对性调优。