星点匠心建站 星点匠心建站XD JIANXIN STUDIO
400-883-9527 免费定制方案
ARTICLE DETAIL

资讯详情

深耕新锐建站与运营推广的一线实战洞察。

MyBatis批量插入性能优化实战:从5分钟到3秒

MyBatis批量插入性能优化实战:从5分钟到3秒 1. 问题背景为什么MyBatis批量插入会慢到5分钟最近在优化一个数据迁移工具时发现用MyBatis批量插入10万条记录竟然需要5分钟。这显然不符合业务需求于是我开始系统性地排查性能瓶颈。经过分析主要存在以下三个关键问题1.1 JDBC驱动默认配置的陷阱大多数开发者不知道的是MySQL JDBC驱动默认会开启rewriteBatchedStatementsfalse。这意味着即使你在代码中调用了批量插入方法驱动底层仍然会一条条执行SQL。我通过JDBC日志确认了这一点 - 控制台里打印的是无数条独立的insert语句。重要提示检查你的JDBC连接字符串如果没有显式设置rewriteBatchedStatementstrue那么你的批量插入实际上是在做单条插入1.2 MyBatis的批量操作实现机制MyBatis的批量操作有两种实现方式ExecutorType.SIMPLE默认模式为每条语句创建新的预处理语句ExecutorType.BATCH复用预处理语句如果没有显式指定BATCH模式即使调用了批量插入方法性能也不会有本质提升。更糟糕的是有些开发者虽然开了BATCH模式但错误地在循环中混用了select和insert操作这会导致MyBatis强制刷新批处理队列。1.3 事务与连接管理问题我看到很多代码是这样写的Transactional public void batchInsert(ListEntity list) { for(Entity item : list) { mapper.insert(item); } }这种写法存在两个致命问题没有使用真正的批量插入API大事务导致数据库锁持有时间过长2. 第一个关键优化JDBC参数调优2.1 必须开启的JDBC参数在数据库连接URL中添加以下关键参数jdbc:mysql://localhost:3306/db?rewriteBatchedStatementstrueuseServerPrepStmtstruecachePrepStmtstrueprepStmtCacheSize250prepStmtCacheSqlLimit2048参数说明rewriteBatchedStatementstrue启用真正的批量语句重写useServerPrepStmtstrue使用服务端预处理语句cachePrepStmtstrue缓存预处理语句prepStmtCacheSize250预处理语句缓存数量prepStmtCacheSqlLimit2048能被缓存的SQL最大长度2.2 实测效果对比优化前默认参数10万条记录约300秒平均TPS333条/秒优化后10万条记录约15秒平均TPS6666条/秒3. 第二个关键优化MyBatis执行模式改造3.1 正确的批量插入姿势// 必须使用BATCH执行器 SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH); try { Mapper mapper session.getMapper(Mapper.class); for (int i 0; i list.size(); i) { mapper.insert(list.get(i)); // 每1000条提交一次避免内存溢出 if (i % 1000 0 || i list.size() - 1) { session.commit(); session.clearCache(); } } } finally { session.close(); }3.2 避免的常见错误不要在循环中混用select和insert操作不要忘记定期clearCache()批量大小建议控制在500-2000之间4. 第三个关键优化多线程并行处理4.1 分片并行处理方案对于超大数据量百万级以上可以采用分片并行处理// 创建线程池 ExecutorService executor Executors.newFixedThreadPool(8); // 数据分片 ListListEntity partitions Lists.partition(fullList, 20000); // 并行处理 ListFuture? futures new ArrayList(); for (ListEntity partition : partitions) { futures.add(executor.submit(() - batchInsert(partition))); } // 等待所有任务完成 for (Future? future : futures) { future.get(); }4.2 并行处理的注意事项线程数不要超过数据库连接池最大连接数每个分片大小建议在1万-5万条之间需要监控数据库服务器负载可能需要调整数据库的max_allowed_packet参数5. 综合优化效果对比优化阶段10万条耗时TPS备注原始方案300s333默认参数简单循环插入JDBC优化15s6666开启批量重写MyBatis优化8s12500使用BATCH执行器多线程优化3s333338线程并行6. 高级技巧与避坑指南6.1 批量插入的ID获取问题如果使用自增ID在BATCH模式下不能立即获取ID。解决方案改用批量插入后查询使用UUID等业务主键使用JDBC的RETURN_GENERATED_KEYS6.2 超大批量数据的处理对于千万级数据考虑使用LOAD DATA INFILE分多个批次在不同时段执行临时关闭索引和约束6.3 不同数据库的差异MySQL需要rewriteBatchedStatementsOracle需要设置defaultExecuteTypeBATCHPostgreSQL需要reWriteBatchedInsertstrue7. 监控与调优建议监控指标数据库服务器CPU/内存网络IO数据库锁等待调优方向调整批量大小优化线程池大小调整数据库参数如innodb_buffer_pool_size我在实际项目中应用这些优化后不仅解决了当前的性能问题还总结出了一套适用于不同场景的批量操作最佳实践。最关键的是要理解每个优化背后的原理而不是简单地复制配置。比如rewriteBatchedStatements参数它实际上是将多个insert语句重写为insert into table values(...),(...)...的格式这才是性能提升的关键。
返回列表