别再被MybatisPlus的saveBatch骗了!手把手教你配置MySQL的rewriteBatchedStatements参数实现真批量插入

别再被MybatisPlus的saveBatch骗了!手把手教你配置MySQL的rewriteBatchedStatements参数实现真批量插入

MyBatisPlus批量插入性能陷阱:揭秘rewriteBatchedStatements的实战配置

上周排查一个数据导入模块的性能问题时,发现一个令人震惊的事实——我们团队使用了三年的MyBatisPlus批量插入方法saveBatch,竟然一直在用单条SQL循环执行!这个发现直接导致我们的批量插入性能比预期慢了10倍以上。本文将完整还原这个排查过程,并给出具体的解决方案。

1. MyBatisPlus批量插入的真相

大多数Java开发者在使用MyBatisPlus时,都会自然地认为saveBatch方法就是用来做批量插入的。但实际情况是,在不做特殊配置的情况下,saveBatch只是循环执行单条INSERT语句。我们来看一个简单的性能对比测试:

// 测试代码示例 List<User> userList = generateTestData(10000); long start = System.currentTimeMillis(); userService.saveBatch(userList); System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");

在不配置rewriteBatchedStatements的情况下,插入1万条数据可能需要5-8秒;而正确配置后,同样的数据量只需要500-800毫秒。这种性能差异在大数据量场景下会变得极其明显。

注意:即使使用了saveBatch方法,如果没有正确配置MySQL连接参数,控制台日志中你看到的仍然是大量单条INSERT语句。

2. 配置rewriteBatchedStatements参数

要让MyBatisPlus真正执行批量插入,关键是在MySQL JDBC连接字符串中添加rewriteBatchedStatements=true参数。以下是完整的配置方式:

# application.yml配置示例 spring: datasource: url: jdbc:mysql://localhost:3306/your_db?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC username: your_username password: your_password driver-class-name: com.mysql.cj.jdbc.Driver

配置生效后,MyBatisPlus会将批量插入优化为真正的批量SQL语句,格式类似于:

INSERT INTO user (name, age) VALUES (?, ?), (?, ?), (?, ?);

而不是多条单条INSERT语句。这种批处理方式大幅减少了网络往返和SQL解析开销。

3. 验证配置是否生效

配置完成后,我们需要验证rewriteBatchedStatements是否真正生效。以下是几种验证方法:

  1. 查看执行日志:正确配置后,日志中应该显示批量INSERT语句而非单条INSERT
  2. 性能对比测试:相同数据量下,配置前后应该有5-10倍的性能差异
  3. 使用MySQL通用日志:在MySQL服务器端开启通用查询日志,观察实际执行的SQL

这里提供一个简单的验证代码片段:

@SpringBootTest public class BatchInsertTest { @Autowired private UserService userService; @Test public void testBatchInsertPerformance() { List<User> userList = generateTestData(10000); // 第一次执行,可能有JVM预热影响 userService.saveBatch(userList); // 正式测试 long start = System.currentTimeMillis(); userService.saveBatch(userList); long duration = System.currentTimeMillis() - start; System.out.println("插入10000条数据耗时:" + duration + "ms"); assertTrue(duration < 1000, "批量插入性能不达标"); } }

4. 批量插入的注意事项

即使配置了rewriteBatchedStatements,还有一些特殊情况会导致MyBatisPlus退化为单条插入:

  1. 实体类字段为null:如果批量插入的实体类中有任何字段为null,且没有配置忽略策略,整个批量操作会退化为单条插入
  2. 混合操作:批量操作中同时包含插入和更新时,可能无法使用批量优化
  3. 特定版本问题:某些旧版本的MyBatisPlus或MySQL驱动可能存在兼容性问题

针对字段为null的问题,可以通过以下方式解决:

// 使用FieldStrategy.IGNORED忽略null值检查 @TableField(insertStrategy = FieldStrategy.IGNORED) private String optionalField; // 或者使用自动填充功能 @TableField(fill = FieldFill.INSERT) private Date createTime;

5. 高级优化技巧

除了基本的rewriteBatchedStatements配置外,还有几个可以进一步提升批量插入性能的技巧:

  1. 合理设置batchSize:MyBatisPlus默认的batchSize是1000,可以根据实际情况调整

    // 自定义batchSize userService.saveBatch(userList, 500);
  2. 使用并行流处理:对于超大数据集,可以考虑使用并行流分割处理

    Lists.partition(userList, 1000).parallelStream() .forEach(batch -> userService.saveBatch(batch));
  3. 调整MySQL服务器参数:适当增大max_allowed_packet等参数

  4. 考虑使用LOAD DATA INFILE:对于超大规模数据导入,直接使用MySQL的LOAD DATA命令可能更高效

6. 性能对比数据

以下是我们在测试环境中得到的性能对比数据(插入10万条记录):

配置方式耗时(ms)网络请求次数SQL语句数量
默认配置5200100000100000
仅rewriteBatchedStatements850100100
rewriteBatchedStatements+优化batchSize6205050
并行处理+所有优化3802020

从数据可以看出,正确的配置可以带来数量级的性能提升。在我们的生产环境中,一个原本需要10分钟的数据导入任务,优化后只需要不到1分钟就完成了。