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 存储 Kubernetes
Seata常见问题之mybatisplus的批量插入方法报SQL错误如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
43 0
|
4月前
|
存储 Java 数据库连接
MyBatis Plus中的批量插入:通过开启rewriteBatchedStatements=true
MyBatis Plus中的批量插入:通过开启rewriteBatchedStatements=true
147 0
|
4月前
|
SQL Java 数据库连接
mybatis 中 foreach collection的常用用法
mybatis 中 foreach collection的常用用法
69 1
|
5月前
|
Java 数据库连接 mybatis
mybatis 批量插入
mybatis 批量插入
26 0
|
5月前
|
XML Java 数据库连接
MyBatis中批量操作foreach与BatchExecutor使用详解
MyBatis中批量操作foreach与BatchExecutor使用详解
100 0
|
6月前
|
SQL XML Java
Mybatis插入大量数据效率对比:foreach、SqlSession批量、sql
使用mybatis插入数据执行效率对比,对比三种方式(测试数据库为MySQL), 使用 SqlSessionFactory,每一批数据执行一次提交 使用mybatis-plus框架的insert方法,for循环,每次执行一次插入 使用ibatis,纯sql插入
|
7月前
|
SQL XML Java
mybatis动态sql&choose&foreach&sql 及include & sql中的特殊字符&后台分页实现& 数据版本号处理并发问题
mybatis动态sql&choose&foreach&sql 及include & sql中的特殊字符&后台分页实现& 数据版本号处理并发问题
|
9月前
|
SQL Java 数据库连接
深入解析 MyBatis 中的 <foreach>标签:优雅处理批量操作与动态 SQL
在当今的Java应用程序开发中,数据库操作是一个不可或缺的部分。MyBatis作为一款颇受欢迎的持久层框架,为我们提供了一种优雅而高效的方式来管理数据库操作。在MyBatis的众多特性中,`&lt;foreach&gt;`标签无疑是一个强大的工具,它使得在SQL语句中进行动态循环迭代变得轻而易举。本文将带您深入探索MyBatis中的`&lt;foreach&gt;`标签,揭示其背后的原理和用法。
140 0
|
9月前
java202304java学习笔记第六十六天-ssm-mybatis中dao层实现-动态sql-foreach之1
java202304java学习笔记第六十六天-ssm-mybatis中dao层实现-动态sql-foreach之1
34 0
|
10月前
|
SQL Java 数据库连接
如何使用Mybatis实现批量插入 ?
如何使用Mybatis实现批量插入 ?
49 0