Mybatis-plus的批量插入真的不能用?开销真的很大?真的有去了解吗?

前言

在很多公司,可以从很多人口中听到“不要用 mybatis-plus 的批量插入,它其实也是遍历插入,性能很差的”。真的是这样吗?我们不应该人云亦云,应该自己去看下。
我们先针对这个观点分析下,总结出来他们的看法应该是下面的其中之一:

  1. 遍历插入,反复创建 Connection,众所周知这是一个比较重的操作,所以性能很差。
    这里不用看源码应该也能知道,因为这个和mybatis-plus没关系,和连接有关系,连接池就是为了支持连接复用出现的。连接和连接池不是本章节的重点,就不展开讲了,总的来说这观点是不正确的。
  2. 一条 insert 就一次网络IO,数量多了,这是个很可观且没必要的开销,所以性能差。

走进源码

对这第二个观点,笔者结合源码给出自己的观点

下面给出笔者的一些相关配置(我mybatis-plus的版本是3.5.3.1
pom.xml如下

...
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
...

application.yml如下

spring:
  # 使用默认的连接池库
  datasource:
    url: "jdbc:mysql://***/***?useSSL=false&useUnicode=true&characterEncoding=utf8&ApplicationName=spring-boot-demo&serverTimezone=UTC&allowMultiQueries=true"
    username: "****"
    password: "****"

service如下图


image.png

1 进入 saveBatch 看下

com.baomidou.mybatisplus.extension.service.IService#saveBatch(java.util.Collection<T>)

image.png

发现里面会给我们这个批量操作开启了事务(如果是期望插入一条就成功一条的,这批量方法就不适用了)并且是有限制提交数量的,默认1000。

2 往里ServiceImpl#saveBatch走

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch

image.png

看到了 SqlMethod.INSERT_ONE,很多人都知道了是 mybatis-plus 的批量插入是一条条插入的,但是这个一次次的遍历是真的发送给MySQL了吗?这里留一个疑问。
我们只要记得这里有一个钩子,后面会回调回来执行 sqlSession.insert(sqlStatement, entity)。

3 SqlHelper#executeBatch(Class<?>, Log, Collection<E>, int, BiConsumer<SqlSession,E>)

com.baomidou.mybatisplus.extension.toolkit.SqlHelper#executeBatch(java.lang.Class<?>, org.apache.ibatis.logging.Log, java.util.Collection<E>, int, java.util.function.BiConsumer<org.apache.ibatis.session.SqlSession,E>)

image.png

这里的 sqlSession 是 org.apache.ibatis.session.SqlSession,mybatis 自己定义的类。
我们去看下截图里面的executeBatch

4 SqlHelper#executeBatch(Class<?> entityClass, Log log, Consumer<SqlSession> consumer)

com.baomidou.mybatisplus.extension.toolkit.SqlHelper#executeBatch(java.lang.Class<?>, org.apache.ibatis.logging.Log, java.util.function.Consumer<org.apache.ibatis.session.SqlSession>)

image.png

结合前面的代码就知道了,我这里是到了1000(默认配置1000,并且我批量保存的list超过了1000),就会将内存的sql全部刷到MySQL,然后回去继续遍历,全部遍历完后就会 close。

测试

实践出真知

笔者知道有些同学在实际工作中试过,发现其实 mybatis-plus 的批量插入其实比自己写 xml 的慢很多。这里面涉及的原因蛮多的。先看成功案例,然后再说原因。
代码,配置,版本还是之前说的,这里需要补充下笔者的 mysql 版本和驱动版本:

  • mysql
    5.7.25
  • 驱动


    驱动

    测试代码如下图


    单元测试
@Slf4j
@Service
@RequiredArgsConstructor
public class WaterNumService extends ServiceImpl<WaterNumMapper, WaterNumEntity> {
    private final WaterGeneratorService waterGeneratorService;

    @Transactional(rollbackFor = Exception.class)
    public void generateWaterNum(GenerateWaterNumRequest req) {
        WaterNumGenerateDTO param = WaterNumGenerateDTO.builder()
                .group(req.getGroup())
                .prefix(req.getPrefix())
                .generateNum(req.getGenerateNum())
                .maxNum(req.getMaxNum())
                .fixLen(req.getFixLen())
                .offset(req.getOffset())
                .build();
        List<String> waterNumList = waterGeneratorService.generateWaterNumBatch(param);
        List<WaterNumEntity> entityList = waterNumList.stream().map(waterNum -> {
            WaterNumEntity entity = new WaterNumEntity();
            entity.setRuleGroup(req.getGroup());
            entity.setWaterNum(waterNum);
            return entity;
        }).collect(Collectors.toList());
        long begin = System.currentTimeMillis();
        // mybatis-plus 的批量插入
        saveBatch(entityList);
        log.debug("generateWaterNum#批量插入耗时 : {}ms", System.currentTimeMillis() - begin);
    }
}

运行结果,如下图


运行结果

当然,这个速度和我插入的表,还有我插入的字段的数量,大字段的数量有关系,还有数据库所在的服务器的磁盘也有关系,10000也不算特别多,但是整体上看其实也还能接受的。

那么为什么有些人在工作中也是1W左右的批量插入就特别慢呢?(参考资料 : https://github.com/baomidou/mybatis-plus/issues/2786)
这是因为 mysql-plus 的批量插入对 mysql 的版本和驱动的版本有关。在较旧版本的驱动默认下会将我们期望的一组批量执行的 sql 拆开发送,可以尝试增加参数 rewriteBatchedStatements,具体如下

spring:
  # 使用默认的连接池库
  datasource:
    url: "jdbc:mysql://***/***?useSSL=false&useUnicode=true&characterEncoding=utf8&ApplicationName=spring-boot-demo&serverTimezone=UTC&allowMultiQueries=true&rewriteBatchedStatements=true"
    username: "****"
    password: "****"

rewriteBatchedStatements=true 结合 5.1.13 以上版本的驱动,这样 statement 才会真正执行 executeBatch()。
增加参数后的运行时间如下


image.png

总结

到这里大家应该都清楚的知道了 mybatis-plus 的批量插入虽然是遍历插入,但是不是一个insert就一次io,而是多条insert打包在一起分批发送的,所以性能不会有什么太大问题。不过笔者这里不是鼓吹大家都用这个批量插入就好了,实际工作中会有更多要求,其实这个简单的批量插入是没法满足的。因此,笔者只是提倡可以根据自己工作实际情况决定,但是性能方面就不用太过担心,mybatis-plus也有考虑的,详情请看:通用 insertBatch 为什么放在 service 层处理

扩展

如果使用 mybatis-plus 3.4+ 版本,并且连接的是 MySQL 8.0 或更高版本的数据库,那么 mybatis-plus 将会自动利用 MySQL 8.0 的原生批量插入功能来执行批量插入操作。

具体实现的关键是在 mybatis-plus 的底层使用了 JDBC 的 addBatch 和 executeBatch 方法。当调用 mybatis-plus 的批量新增时,mybatis-plus 会将待插入的对象列表传递给底层的 JDBC 驱动程序。而 MySQL 8.0 的 JDBC 驱动程序会自动将这些插入语句封装成批量插入的 SQL 语句,并一次性发送给数据库执行。

需要注意的是,要确保以下条件满足才能利用 MySQL 8.0 的批量插入功能:

  1. 使用 MySQL 8.0 或更高版本的数据库
  2. 使用兼容 MySQL 8.0 的 JDBC 驱动程序(如 mysql-connector-java 版本 8.0 或更高)
  3. 使用 mybatis-plus 3.4+ 版本
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容