MyBatis批量插入数据优化,那叫一个优雅!

我们使用了 mybatis-plus 框架,并采用其中的 saveBatch 方法进行批量数据插入。然而,通过深入研究源码,我发现这个方法并没有如我期望的那样高效。
首页 新闻资讯 行业资讯 MyBatis批量插入数据优化,那叫一个优雅!

在项目开发中,我们经常需要进行大量数据的批量插入操作。然而,在实际应用中,插入大量数据时性能常常成为一个瓶颈。在我最近的项目中,我发现了一些能够显著提升批量插入性能的方法,并进行了一系列实验来验证它们的有效性。

今日内容介绍,大约花费15分钟

图片图片

背景介绍

我们使用了 mybatis-plus 框架,并采用其中的 saveBatch 方法进行批量数据插入。然而,通过深入研究源码,我发现这个方法并没有如我期望的那样高效

图片图片

这是因为最终在执行的时候还是通过for循环一条条执行insert,然后再一批的进行flush ,默认批的消息为1000

图片图片

为了找到更优秀的解决方案,我展开了一场性能优化的探索之旅。好了我们现在开始探索

实验准备

  • 创建一张表tb_student

createtablespringboot_mp.tb_student(idbigintauto_incrementcomment'主键ID'primarykey,stuidvarchar(40)notnullcomment'学号',namevarchar(30)nullcomment'姓名',agetinyintnullcomment'年龄',sextinyint(1)nullcomment'性别 0 男 1 女',deptvarchar(2000)nullcomment'院系',addressvarchar(400)nullcomment'家庭地址',constraintstuidunique(stuid));
  • 创建spring-boot-mybatis-demo项目并在pom.xml中添加依赖

图片图片

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
  • application.yml配置

server:
  port:8890spring:
  application:
    name: mybatis-demo#指定服务名datasource:
    username: root
    password: root#    url: jdbc:mysql://localhost:3306/springboot_mp?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=trueurl: jdbc:mysql://localhost:3306/springboot_mp?useUnicode=true&characterEncoding=utf8driver-class-name: com.mysql.cj.jdbc.Driver#mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:/mapper/**/*.xml
  • 使用mybatisX生成代码

图片图片

图片图片

图片图片

探索实验

每次都是插入100000条数据

注意:因为我的电脑性能比较好,所以才插入这么多数据,大家可以插入1000进行实验对比


  1. 单条循环插入:传统方法的基准

首先,我采用了传统的单条循环插入方法,将每条数据逐一插入数据库,作为性能对比的基准。

/**
 * @author springboot葵花宝典
 * @description: TODO
 */@SpringBootTestpublicclass MybatisTest {@Autowiredprivate StudentService studentService;@Autowiredprivate SqlSessionFactory sqlSessionFactory;@Testpublicvoid MybatisBatchSaveOneByOne(){


        SqlSession sqlSession=sqlSessionFactory.openSession();try {
            StopWatch stopWatch=new StopWatch();stopWatch.start("mybatis plus save one");for(inti=0;i<100000;i++){
                Student student=new Student();student.setStuid("6840"+i);student.setName("zhangsan"+i);student.setAge((i%100));if(i%2==0){
                    student.setSex(0);}else{
                    student.setSex(1);}

                student.setDept("计算机学院");student.setAddress("广东省广州市番禺"+i+"号");//一条一条插入studentService.save(student);}
            sqlSession.commit();stopWatch.stop();System.out.println("mybatis plus save one:"+stopWatch.getTotalTimeMillis());} finally {
            sqlSession.close();}

    }

}

发现花费了195569毫秒

图片图片

  1. mybatis-plus 的 saveBatch 方法

现在尝试 mybatis-plus 提供的 saveBatch 方法,期望它能够提高性能。

@Testpublicvoid MybatissaveBatch(){


        SqlSession sqlSession=sqlSessionFactory.openSession();try {

            List<Student>students=new ArrayList<>();StopWatch stopWatch=new StopWatch();stopWatch.start("mybatis plus save batch");for(inti=0;i<100000;i++){
                Student student=new Student();student.setStuid("6840"+i);student.setName("zhangsan"+i);student.setAge((i%100));if(i%2==0){
                    student.setSex(0);}else{
                    student.setSex(1);}

                student.setDept("计算机学院");student.setAddress("广东省广州市番禺"+i+"号");//一条一条插入students.add(student);}

            studentService.saveBatch(students);sqlSession.commit();stopWatch.stop();System.out.println("mybatis plus save batch:"+stopWatch.getTotalTimeMillis());} finally {
            sqlSession.close();}

    }

发现花费9204毫秒,比一条条插入数据性能提高十几倍

图片

3.手动拼接 SQL:挑战传统的方式

<insertid="saveBatch2">insertintospringboot_mp.tb_student(stuid,name,age,sex,dept,address)values<foreach collection="students"item="stu"index="index"separator=",">(#{stu.stuid}, #{stu.name}, #{stu.age}, #{stu.sex}, #{stu.dept}, #{stu.address})</foreach></insert>

发现花费10958毫秒,比一条条插入数据性能提高十几倍,但是和saveBatch性能相差不大

既然都验证都这了,我就在想,要不要使用JDBC批量插入进行验证一下,看会不会出现原始的才是最好的结果

4.JDBC 的 executeBatch 方法

尝试直接使用 JDBC 提供的 executeBatch 方法,看是否有意外的性能提升。

@Testpublicvoid JDBCSaveBatch()throws SQLException {


        SqlSession sqlSession=sqlSessionFactory.openSession();Connection connection=sqlSession.getConnection();connection.setAutoCommit(false);Stringsql="insert into springboot_mp.tb_student ( stuid, name, age, sex, dept, address) values (?, ?, ?, ?, ?, ?);";try(PreparedStatement statement=connection.prepareStatement(sql)){

            List<Student>students=new ArrayList<>();StopWatch stopWatch=new StopWatch();stopWatch.start("mybatis plus JDBCSaveBatch");for(inti=0;i<100000;i++){
                statement.setString(1,"6840"+i);statement.setString(2,"zhangsan"+i);statement.setInt(3,(i%100));if(i%2==0){
                    statement.setInt(4,0);}else{
                    statement.setInt(4,1);}
                statement.setString(5,"计算机学院");statement.setString(6,"广东省广州市番禺"+i+"号");statement.addBatch();}

            statement.executeBatch();connection.commit();stopWatch.stop();System.out.println("mybatis plus JDBCSaveBatch:"+stopWatch.getTotalTimeMillis());}
        catch(Exception e){
            System.out.println(e.getMessage());}
        finally {

            sqlSession.close();}

JDBC executeBatch 的性能会好点,耗费6667毫秒

图片

但是感觉到这里以后,觉得时候还是比较长,有没有可以再进行优化的方式,然后我就在ClientPreparedStatement类中发现有一个叫做rewriteBatchedStatements 的属性,从名字来看是要重写批操作的 Statement,前面batchHasPlainStatements 已经是 false,取反肯定是 true,所以只要这参数是 true 就会进行一波操作。rewriteBatchedStatements默认是 false。

图片图片

图片

图片图片

大家也可以自行网上搜索一下这个神奇的属性然后我在url添加上这个属性

图片图片

然后继续跑了下 mybatis-plus 自带的 saveBatch,果然性能大大提高直接由原来的9204毫秒,提升到现在的3903毫秒

图片图片

再来跑一下JDBC 的 executeBatch ,果然也提高了。

直接由原来的6667毫秒,提升到了3794毫秒

图片

结果对比

批量保存方式

数据量(条)

耗时(ms)

单条循环插入

100000

195569

mybatis-plus saveBatch


100000

9204

mybatis-plus saveBatch(添加 rewrite 参数)

100000

3903

手动拼接 SQL

100000

6667

JDBC executeBatch

100000

10958

JDBC executeBatch(添加 rewrite 参数)

100000

3794

结论

通过实验结果,我们可以得出以下结论:

  • mybatis-plus 的 saveBatch 方法相比单条循环插入在性能上有所提升,但仍然不够理想。

  • JDBC 的 executeBatch 方法在默认情况下性能与 mybatis-plus 的 saveBatch 类似,但通过设置 rewriteBatchedStatements 参数为 true 可显著提高性能。

  • rewriteBatchedStatements 参数的作用是将一批插入拼接成 insert into xxx values (a),(b),(c)... 这样的一条语句形式,提高了性能。

优化建议

如果您在项目中需要进行批量插入操作,我建议考虑以下优化方案:

  • 如果使用 mybatis-plus,可以尝试将 JDBC 连接字符串中的 rewriteBatchedStatements 参数设置为 true,以提高 saveBatch 方法的性能。

44    2023-12-30 20:04:51    MyBatis 框架 数据