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
在这里插入图片描述
对应的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 UserTest () {
    }

    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 左右
在这里插入图片描述

2. 批量测试

  • 100 条, 平均总耗时为 34ms,平均单条耗时0.34ms
    在这里插入图片描述
  • 500 条, 平均总耗时为 117ms,平均单条耗时0.23ms
    在这里插入图片描述
  • 2000 条, 平均总耗时为 340ms,平均单条耗时0.17ms
    在这里插入图片描述
  • 5000 条, 平均总耗时为 700ms,平均单条耗时0.14ms
    在这里插入图片描述
  • 10000 条, 平均总耗时为 1361ms,平均单条耗时0.14ms
    在这里插入图片描述
    后经查看数据库,上述数据均准确完成插入,所以测试是有效的,一次语句插入10000条以上一般比较少见,不再往上测试
    在这里插入图片描述

三、加大载荷

从上述测验可以看出,没有出现预料中的性能问题,考虑是否是字段还不够多、或单字段太短导致的?因此再次做出调整,
首先将VARCHAR字段数量增加至25个,长度也不再控制,使用默认的255.
在这里插入图片描述
然后实际使用的字符串长度,保持和日常经验一致的15字符左右
在这里插入图片描述
加大载荷后,我们直接从2000开始测试,五次测试结果如下:
在这里插入图片描述

结果为:
2000 条, 平均总耗时为 500ms,平均单条耗时0.25ms
5000 条, 平均总耗时为 1187ms,平均单条耗时0.23ms
10000 条, 平均总耗时为 2465ms,平均单条耗时0.25ms

检查数据库也是正常插入完成
在这里插入图片描述

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

四、测试结论

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

无法复现,未出现性能问题,验证失败!
初步怀疑是 mybatis 与 jdbc 版本升级后,导致以前的性能问题得到解决,有待继续确认。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战斧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值