MyBatis痛点验证,使用 foreach 批量插入慢?

简介: MyBatis痛点验证,使用 foreach 批量插入慢?

前言

mybatis的动态sql一直广受好评,因为节约了大量手动sql的麻烦,尤其是其中的foreach标签,在执行批量操作时,简直如虎添翼。但是相信不少人在使用中发现,使用foreach做批量操作有时会无与伦比的慢,可能长达数十秒甚至分钟级别,我们今天就来验证一下该现象


一、准备工作

1. 环境准备

系统:windows 10
CPU:I7-6700K
数据库:mysql8.0
工程:springboot 2.5.2
版本:mybatis 3.5.6
JDBC驱动:8.0.25

2. 创建资源

建立一张 id + 15个 varchar 字段的表 usertest

0bb16578d3114b89a88b479d302f7e2c.png

对应的java对象及构造方法:

@Data
public class UserTest {
    private Integer id;
    private String name1;
    private String name2;
    private String name3;
    private String name4;
    private String name5;
    private String name6;
    private String name7;
    private String name8;
    private String name9;
    private String name10;
    private String name11;
    private String name12;
    private String name13;
    private String name14;
    private String name15;
    public () {
    }
    public UserTest(Integer id, String name1) {
        this.id = id;
        this.name1 = name1;
        this.name2 = name1 + "a";
        this.name3 = name1 + "b";
        this.name4 = name1 + "c";
        this.name5 = name1 + "d";
        this.name6 = name1 + "e";
        this.name7 = name1 + "f";
        this.name8 = name1 + "g";
        this.name9 = name1 + "h";
        this.name10 = name1 + "i";
        this.name11 = name1 + "j";
        this.name12 = name1 + "k";
        this.name13 = name1 + "l";
        this.name14 = name1 + "m";
        this.name15 = name1 + "n";
    }
}

使用 foreah 的动态sql则如下

    <insert id="addUser" parameterType="com.zhanfu.springboot.demo.entity.UserTest" >
        insert  into usertest(id,name1,name2,name3,name4,name5,name6,name7
        ,name8,name9,name10,name11,name12,name13,name14,name15) values
        <foreach collection="list" item="item" separator=",">
            (
             #{item.id}, #{item.name1},#{item.name2},#{item.name3},#{item.name4},#{item.name5},
            #{item.name6},#{item.name7},#{item.name8},#{item.name9},#{item.name10},#{item.name11},
            #{item.name12},#{item.name13},#{item.name14},#{item.name15}
             )
        </foreach>
    </insert>

二、执行测试

先写可以一直运行的方法:

  private int sequence = 1;
    public boolean addUser(Integer count) {
        List<UserTest> list = new ArrayList<>();
        int max = sequence + count;
        for (; sequence < max; sequence++) {
            UserTest usertest = new UserTest(sequence , sequence + "zf");
            list.add(usertest);
        }
        long start = System.currentTimeMillis();
        boolean res = userMapper.addUser(list);
        System.out.println("列表大小" +list.size()+ ",耗时:" + (System.currentTimeMillis() - start));
        return res;
    }

1. 基准测试

我们先看只插入一个对象时,使用的时间,可以看到在第一次插入时,耗时最长,因为此时与数据库连接等操作需要执行,可忽略第一次的数据。

我们以后面5次的耗时为准,可以看到,对象的插入平均耗时在 8ms 左右

50cea88e09e24259af37d629e9344dcf.png


2. 批量测试

100 条, 平均总耗时为 34ms,平均单条耗时0.34ms

32f7070eee734149b3668bd538c393eb.png

500 条, 平均总耗时为 117ms,平均单条耗时0.23ms

286c7fce6b9d48d9afc63cf7beff6747.png

2000 条, 平均总耗时为 340ms,平均单条耗时0.17ms

6f2585412e604e9ab01a6be4717a47eb.png

5000 条, 平均总耗时为 700ms,平均单条耗时0.14ms

fbe58dd88cff42d5ad9caa73ee33afed.png

10000 条, 平均总耗时为 1361ms,平均单条耗时0.14ms

fe5696969e854070b01f53e10cfa4994.png

后经查看数据库,上述数据均准确完成插入,所以测试是有效的,一次语句插入10000条以上一般比较少见,不再往上测试

eb897473e9b743d986946caed9ef1eb0.png

三、加大载荷

从上述测验可以看出,没有出现预料中的性能问题,考虑是否是字段还不够多、或单字段太短导致的?因此再次做出调整,

首先将VARCHAR字段数量增加至25个,长度也不再控制,使用默认的255.

e00a66018e8d41218fe0dc46b2c4d406.png

然后实际使用的字符串长度,保持和日常经验一致的15字符左右

1a2f49bd0d95488c8895dbdcd218d75e.png

加大载荷后,我们直接从2000开始测试,五次测试结果如下:

a097cf0086814c619366a72cf3176c5e.png


结果为:

2000 条, 平均总耗时为 500ms,平均单条耗时0.25ms

5000 条, 平均总耗时为 1187ms,平均单条耗时0.23ms

10000 条, 平均总耗时为 2465ms,平均单条耗时0.25ms


检查数据库也是正常插入完成

91b587a678d34cecbb3d83765d5c98d2.png


相比上次的测试结果,数据越多,显得越慢。

在10000条的时候,相比之前耗时增长了大约80%,但考虑到表格从16个字段增加至26个字段,本身就增加了60%多。所以这样的情况是完全在预期中的,距离实机那种插入几百行到千行,就会长达几十秒甚至分钟级别的耗时仍有着数量级差距。


四、测试结论

在测试中,即使到了一次批量插入10000条数据,仍然没有出现问题,平均单条耗时反而越来越短。但是在生产环境中,却屡次出现性能问题,甚至在千条数据规模时,能达到几十秒,其中数据库执行耗时在百毫秒的范围内,确定原因是拼接sql过程耗时太长。


无法复现,未出现性能问题,验证失败!

初步怀疑是 mybatis 与 jdbc 版本升级后,导致以前的性能问题得到解决,有待继续确认。

目录
相关文章
|
2月前
|
SQL XML Java
mybatis复习03,动态SQL,if,choose,where,set,trim标签及foreach标签的用法
文章介绍了MyBatis中动态SQL的用法,包括if、choose、where、set和trim标签,以及foreach标签的详细使用。通过实际代码示例,展示了如何根据条件动态构建查询、更新和批量插入操作的SQL语句。
mybatis复习03,动态SQL,if,choose,where,set,trim标签及foreach标签的用法
|
3月前
|
存储 SQL Java
MyBatis batchInsert 批量插入数据
MyBatis batchInsert 批量插入数据
80 0
|
6月前
|
SQL Java 数据库连接
Mybatis的批量插入Bigdecimal会丢失精度
Mybatis的批量插入Bigdecimal会丢失精度
451 0
|
6月前
|
SQL 存储 Kubernetes
Seata常见问题之mybatisplus的批量插入方法报SQL错误如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
236 0
|
6月前
|
SQL Java 数据库连接
mybatis 中 foreach collection的常用用法
mybatis 中 foreach collection的常用用法
199 1
|
6月前
|
存储 Java 数据库连接
MyBatis Plus中的批量插入:通过开启rewriteBatchedStatements=true
MyBatis Plus中的批量插入:通过开启rewriteBatchedStatements=true
656 0
|
6月前
|
Java 数据库连接 mybatis
mybatis 批量插入
mybatis 批量插入
39 0
|
6月前
|
XML Java 数据库连接
MyBatis中批量操作foreach与BatchExecutor使用详解
MyBatis中批量操作foreach与BatchExecutor使用详解
785 0
Mybatis插入大量数据效率对比:foreach、SqlSession批量、sql
使用mybatis插入数据执行效率对比,对比三种方式(测试数据库为MySQL), 使用 SqlSessionFactory,每一批数据执行一次提交 使用mybatis-plus框架的insert方法,for循环,每次执行一次插入 使用ibatis,纯sql插入
|
SQL Java 数据库连接
63MyBatis - foreach元素(复习)
63MyBatis - foreach元素(复习)
59 0