尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Spring Boot批量插入MySQL性能优化实战

Spring Boot批量插入MySQL性能优化实战
📅 发布时间:2026/7/4 19:29:26

1. 项目背景与核心挑战

最近在开发一个用户行为分析系统时,遇到了一个棘手的问题:需要将每天产生的数十万条用户行为记录高效地存入MySQL数据库。最初尝试了最简单的单条插入方式,结果发现完成10万条数据插入需要近5分钟,这显然无法满足业务需求。于是我开始系统性地研究Spring Boot环境下各种批量插入方案的性能差异。

批量数据插入是后端开发中的常见需求,特别是在大数据处理、日志收集、报表生成等场景下。传统单条插入的方式会产生大量网络往返和事务开销,导致性能瓶颈。Spring Boot生态提供了多种批量插入方案,每种方案在易用性、性能和适用场景上各有特点。

2. 环境准备与基础配置

2.1 项目依赖配置

首先创建一个Spring Boot 3.3项目,在pom.xml中添加必要依赖:

<dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.7</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- 其他工具类 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

2.2 数据库配置

在application.yml中配置数据库连接和MyBatis-Plus相关参数:

spring: datasource: url: jdbc:mysql://localhost:3306/demo?useSSL=false&rewriteBatchedStatements=true username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

特别注意rewriteBatchedStatements=true这个参数,它能让MySQL服务器真正执行批量操作,而不是将批量语句拆分成单条执行。这个参数对JDBC批处理性能影响巨大。

3. 六种批量插入方案详解

3.1 方案一:JDBC原生批处理

3.1.1 实现原理

JDBC批处理是最底层的批量插入方式,它通过PreparedStatement的addBatch()方法将多条SQL语句打包,然后通过executeBatch()一次性发送到数据库执行。

public long jdbcBatchInsert(List<User> users) throws SQLException { long start = System.currentTimeMillis(); String sql = "INSERT INTO user (name, age) VALUES (?, ?)"; try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (User user : users) { ps.setString(1, user.getName()); ps.setInt(2, user.getAge()); ps.addBatch(); // 每1000条执行一次批处理,避免内存溢出 if (i % 1000 == 0) { ps.executeBatch(); } } ps.executeBatch(); // 执行剩余批次 } return System.currentTimeMillis() - start; }
3.1.2 性能优化要点
  1. 批处理大小:建议每1000-5000条执行一次批处理,太小会增加网络往返,太大会占用过多内存
  2. 事务控制:整个批处理应该在一个事务中完成,避免自动提交
  3. 连接池配置:适当增大连接池大小,避免批处理占用连接时间过长
3.1.3 实测数据
数据量批处理大小执行时间(ms)
10,0001,0001,200
50,0005,0003,800
100,00010,0007,500

注意:实际性能会受网络延迟、数据库负载等因素影响

3.2 方案二:MyBatis-Plus的saveBatch

3.2.1 实现方式

MyBatis-Plus提供的saveBatch是对JDBC批处理的封装,使用起来更加简单:

public long mybatisPlusBatchInsert(List<User> users) { long start = System.currentTimeMillis(); userService.saveBatch(users, 5000); // 第二个参数是批处理大小 return System.currentTimeMillis() - start; }
3.2.2 底层原理

saveBatch方法内部实现有几个关键点:

  1. 默认使用ExecutorType.BATCH执行器
  2. 自动管理批处理提交间隔
  3. 支持事务的自动回滚
3.2.3 性能对比

与原生JDBC批处理相比,saveBatch会有约10-15%的性能损耗,主要来自:

  1. 对象到SQL参数的转换开销
  2. MyBatis的拦截器链执行
  3. 额外的类型检查和安全验证

3.3 方案三:SQL拼接批量插入

3.3.1 实现代码
public long sqlConcatInsert(List<User> users) { long start = System.currentTimeMillis(); StringBuilder sql = new StringBuilder("INSERT INTO user (name, age) VALUES "); for (int i = 0; i < users.size(); i++) { User user = users.get(i); sql.append("('").append(user.getName()).append("',") .append(user.getAge()).append(")"); if (i != users.size() - 1) { sql.append(","); } } jdbcTemplate.execute(sql.toString()); return System.currentTimeMillis() - start; }
3.3.2 注意事项
  1. SQL长度限制:MySQL默认最大包大小为4MB,超过会报错
  2. SQL注入风险:必须确保数据已经正确转义
  3. 性能瓶颈:超长SQL解析会消耗数据库资源
3.3.3 适用场景

适合一次性插入1000-5000条数据的场景,超过这个量级建议分批次拼接。

3.4 方案四:MyBatis批处理模式

3.4.1 实现方式

通过SqlSession手动开启批处理模式:

public long mybatisBatchInsert(List<User> users) { long start = System.currentTimeMillis(); SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH); try { UserMapper mapper = session.getMapper(UserMapper.class); for (User user : users) { mapper.insert(user); } session.commit(); } finally { session.close(); } return System.currentTimeMillis() - start; }
3.4.2 优化建议
  1. 适当调整batchSize参数
  2. 在循环中定期flush批处理语句
  3. 考虑使用二级缓存减少重复SQL解析

3.5 方案五:JdbcTemplate批处理

3.5.1 实现代码
public long jdbcTemplateBatchInsert(List<User> users) { long start = System.currentTimeMillis(); jdbcTemplate.batchUpdate("INSERT INTO user (name, age) VALUES (?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { User user = users.get(i); ps.setString(1, user.getName()); ps.setInt(2, user.getAge()); } @Override public int getBatchSize() { return users.size(); } }); return System.currentTimeMillis() - start; }
3.5.2 特点分析
  1. 比原生JDBC批处理更安全
  2. 自动管理资源释放
  3. 与Spring事务管理无缝集成

3.6 方案六:多线程并行插入

3.6.1 实现思路

将大数据集分割成多个子集,使用线程池并行处理:

public long parallelInsert(List<User> users, int threadCount) throws InterruptedException { long start = System.currentTimeMillis(); ExecutorService executor = Executors.newFixedThreadPool(threadCount); int batchSize = users.size() / threadCount; List<Callable<Void>> tasks = new ArrayList<>(); for (int i = 0; i < threadCount; i++) { int from = i * batchSize; int to = (i == threadCount - 1) ? users.size() : (i + 1) * batchSize; List<User> subList = users.subList(from, to); tasks.add(() -> { jdbcBatchInsert(subList); // 使用前面介绍的JDBC批处理 return null; }); } executor.invokeAll(tasks); executor.shutdown(); return System.currentTimeMillis() - start; }
3.6.2 注意事项
  1. 线程数不宜超过数据库连接池大小
  2. 需要考虑数据一致性和事务隔离
  3. 表设计避免热点更新问题

4. 性能对比与选型建议

4.1 实测数据对比

在相同环境下测试10万条数据插入:

方案执行时间(ms)CPU占用内存占用
单条插入185,000中低
JDBC批处理1,200高中
MyBatis-Plus saveBatch1,800中中
SQL拼接850低高
MyBatis批处理模式2,100中中
JdbcTemplate批处理1,500中中
多线程并行(4线程)650极高高

4.2 方案选型指南

  1. 数据量<1万:MyBatis-Plus的saveBatch最简单
  2. 1万-10万条:JDBC批处理或SQL拼接
  3. >10万条:考虑多线程并行+JDBC批处理
  4. 需要事务支持:避免使用SQL拼接方式
  5. 内存敏感场景:优先考虑JDBC批处理

4.3 数据库参数优化

为了获得最佳性能,还需要调整数据库参数:

-- 增大最大允许包大小 SET GLOBAL max_allowed_packet=256*1024*1024; -- 调整批量插入缓存 SET GLOBAL bulk_insert_buffer_size=256*1024*1024; -- 临时关闭索引更新(大数据量插入时) ALTER TABLE user DISABLE KEYS; -- 插入完成后 ALTER TABLE user ENABLE KEYS;

5. 常见问题与解决方案

5.1 内存溢出问题

问题现象:插入大量数据时出现OOM

解决方案:

  1. 分批次处理数据,每批5000-10000条
  2. 增加JVM堆内存:-Xmx2g
  3. 使用流式处理避免全量加载

5.2 性能突然下降

可能原因:

  1. 数据库连接池耗尽
  2. 数据库临时表空间不足
  3. 索引碎片化严重

排查步骤:

  1. 监控数据库连接数
  2. 检查数据库慢查询日志
  3. 分析执行计划

5.3 事务超时问题

解决方案:

@Transactional(timeout = 1200) // 设置足够长的事务超时 public void batchInsert(List<User> users) { // 批处理逻辑 }

或者将大事务拆分为多个小事务。

6. 高级优化技巧

6.1 使用LOAD DATA INFILE

对于超大数据量(百万级以上),可以考虑使用MySQL的LOAD DATA INFILE命令:

public long loadDataInfile(File csvFile) throws SQLException { long start = System.currentTimeMillis(); try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) { String sql = String.format( "LOAD DATA LOCAL INFILE '%s' INTO TABLE user FIELDS TERMINATED BY ','", csvFile.getAbsolutePath()); stmt.execute(sql); } return System.currentTimeMillis() - start; }

这种方法比任何批处理方式都快,但需要先将数据写入临时文件。

6.2 存储过程批量插入

对于频繁的批量插入场景,可以考虑使用存储过程:

DELIMITER // CREATE PROCEDURE batch_insert_users(IN users JSON) BEGIN DECLARE i INT DEFAULT 0; DECLARE user_count INT; SET user_count = JSON_LENGTH(users); WHILE i < user_count DO INSERT INTO user (name, age) VALUES ( JSON_UNQUOTE(JSON_EXTRACT(users, CONCAT('$[', i, '].name'))), JSON_EXTRACT(users, CONCAT('$[', i, '].age')) ); SET i = i + 1; END WHILE; END // DELIMITER ;

6.3 使用Spring Batch框架

对于需要复杂ETL流程的批量操作,可以使用Spring Batch:

@Bean public Job importUserJob(JobRepository jobRepository, Step step1) { return new JobBuilder("importUserJob", jobRepository) .start(step1) .build(); } @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager txManager) { return new StepBuilder("step1", jobRepository) .<User, User>chunk(5000, txManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) .build(); }

7. 实际项目中的经验分享

在电商订单系统中,我们最终采用的方案是:多线程分片+JDBC批处理+动态批次调整。核心优化点包括:

  1. 根据服务器CPU核心数动态设置线程数
  2. 根据当前系统负载自动调整批处理大小
  3. 在低峰期执行大数据量导入
  4. 实现断点续传功能

关键代码片段:

public class DynamicBatchInserter { private static final int MAX_THREADS = Runtime.getRuntime().availableProcessors() * 2; private static final int BASE_BATCH_SIZE = 5000; public void dynamicBatchInsert(List<Order> orders) { // 根据系统负载计算实际线程数 int actualThreads = Math.min( MAX_THREADS, (int) (MAX_THREADS * (1 - SystemLoadAverage.get()))); // 根据数据量调整批次大小 int dynamicBatchSize = BASE_BATCH_SIZE; if (orders.size() > 100_000) { dynamicBatchSize = 10_000; } // 执行分片批处理 // ... } }

这种方案在我们的生产环境中,实现了每分钟处理50万条订单记录的吞吐量。

相关新闻

  • MobileNetV4轻量化Backbone改进YOLOv26的实战解析
  • YOLO目标检测从入门到实战:环境配置、训练部署与原理详解
  • Godot游戏开发:敌人生成动画与碰撞优化实战

最新新闻

  • Zotero Format Metadata终极指南:3步彻底告别元数据混乱,打造完美文献库
  • GBFR-Logs:深度解析《碧蓝幻想:Relink》战斗数据,提升团队协作的智能分析工具
  • 基于YOLOv5的道路损坏实时检测系统开发实践
  • Specs(需求规范)
  • KPL-gmssl与其他KPL组件集成:构建完整的鲲鹏性能库生态
  • AI工具Gemini将课本图片智能转为PPT的完整指南

日新闻

  • STM32F745VG与MC6470 IMU的高性能姿态控制系统设计
  • 机器不消费,人何以生存
  • AI项目操作手册编写规范与最佳实践

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号