从120s到2.5s,看看人家的MyBatis批量插入数据优化,那叫一个优雅

news2024/9/21 2:46:25

最近在压测一批接口,发现接口处理速度慢的有点超出预期,感觉很奇怪,后面定位发现是数据库批量保存这块很慢。

这个项目用的是 mybatis-plus,批量保存直接用的是 mybatis-plus 提供的 saveBatch。

我点进去看了下源码,感觉有点不太对劲:

图片

我继续追踪了下,从这个代码来看,确实是 for 循环一条一条执行了 sqlSession.insert,下面的 consumer 执行的就是上面的 sqlSession.insert:

图片

然后累计一定数量后,一批 flush。

从这点来看,这个 saveBach 的性能肯定比直接一条一条 insert 快。

我直接进行一个粗略的实验,简单创建了一张表来对比一波!

粗略的实验

1000条数据,一条一条插入

@Test
void MybatisPlusSaveOne() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("mybatis plus save one");
        for (int i = 0; i < 1000; i++) {
            OpenTest openTest = new OpenTest();
            openTest.setA("a" + i);
            openTest.setB("b" + i);
            openTest.setC("c" + i);
            openTest.setD("d" + i);
            openTest.setE("e" + i);
            openTest.setF("f" + i);
            openTest.setG("g" + i);
            openTest.setH("h" + i);
            openTest.setI("i" + i);
            openTest.setJ("j" + i);
            openTest.setK("k" + i);
            //一条一条插入
            openTestService.save(openTest);
        }
        sqlSession.commit();
        stopWatch.stop();
        log.info("mybatis plus save one:" + stopWatch.getTotalTimeMillis());
    } finally {
        sqlSession.close();
    }
}

图片

可以看到,执行一批 1000 条数的批量保存,耗费的时间是 121011 毫秒。

1000条数据用 mybatis-plus 自带的 saveBatch 插入

@Test
void MybatisPlusSaveBatch() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        List<OpenTest> openTestList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            OpenTest openTest = new OpenTest();
            openTest.setA("a" + i);
            openTest.setB("b" + i);
            openTest.setC("c" + i);
            openTest.setD("d" + i);
            openTest.setE("e" + i);
            openTest.setF("f" + i);
            openTest.setG("g" + i);
            openTest.setH("h" + i);
            openTest.setI("i" + i);
            openTest.setJ("j" + i);
            openTest.setK("k" + i);
            openTestList.add(openTest);
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("mybatis plus save batch");
        //批量插入
        openTestService.saveBatch(openTestList);
        sqlSession.commit();
        stopWatch.stop();
        log.info("mybatis plus save batch:" + stopWatch.getTotalTimeMillis());
    } finally {
        sqlSession.close();
    }
}

图片

耗费的时间是 59927 毫秒,比一条一条插入快了一倍,从这点来看,效率还是可以的。

然后常见的还有一种利用拼接 sql 方式来实现批量插入,我们也来对比试试看性能如何。

1000条数据用手动拼接 sql 方式插入

搞个手动拼接:

图片来跑跑下性能如何:

@Test
void MapperSaveBatch() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        List<OpenTest> openTestList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            OpenTest openTest = new OpenTest();
            openTest.setA("a" + i);
            openTest.setB("b" + i);
            openTest.setC("c" + i);
            openTest.setD("d" + i);
            openTest.setE("e" + i);
            openTest.setF("f" + i);
            openTest.setG("g" + i);
            openTest.setH("h" + i);
            openTest.setI("i" + i);
            openTest.setJ("j" + i);
            openTest.setK("k" + i);
            openTestList.add(openTest);
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("mapper save batch");
        //手动拼接批量插入
        openTestMapper.saveBatch(openTestList);
        sqlSession.commit();
        stopWatch.stop();
        log.info("mapper save batch:" + stopWatch.getTotalTimeMillis());
    } finally {
        sqlSession.close();
    }
}

图片

耗时只有 2275 毫秒,性能比 mybatis-plus 自带的 saveBatch 好了 26 倍!

这时,我又突然回想起以前直接用 JDBC 批量保存的接口,那都到这份上了,顺带也跑跑看!

1000条数据用 JDBC executeBatch 插入

@Test
void JDBCSaveBatch() throws SQLException {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    Connection connection = sqlSession.getConnection();
    connection.setAutoCommit(false);

    String sql = "insert into open_test(a,b,c,d,e,f,g,h,i,j,k) values(?,?,?,?,?,?,?,?,?,?,?)";
    PreparedStatement statement = connection.prepareStatement(sql);
    try {
        for (int i = 0; i < 1000; i++) {
            statement.setString(1,"a" + i);
            statement.setString(2,"b" + i);
            statement.setString(3, "c" + i);
            statement.setString(4,"d" + i);
            statement.setString(5,"e" + i);
            statement.setString(6,"f" + i);
            statement.setString(7,"g" + i);
            statement.setString(8,"h" + i);
            statement.setString(9,"i" + i);
            statement.setString(10,"j" + i);
            statement.setString(11,"k" + i);
            statement.addBatch();
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("JDBC save batch");
        statement.executeBatch();
        connection.commit();
        stopWatch.stop();
        log.info("JDBC save batch:" + stopWatch.getTotalTimeMillis());
    } finally {
        statement.close();
        sqlSession.close();
    }
}

图片

耗时是 55663 毫秒,所以 JDBC executeBatch 的性能跟 mybatis-plus 的 saveBatch 一样(底层一样)。

综上所述,拼接 sql 的方式实现批量保存效率最佳。

但是我又不太甘心,总感觉应该有什么别的法子,然后我就继续跟着 mybatis-plus 的源码 debug 了一下,跟到了 mysql 的驱动,突然发现有个 if 里面的条件有点显眼:

图片

就是这个叫 rewriteBatchedStatements 的玩意,从名字来看是要重写批操作的 Statement,前面batchHasPlainStatements 已经是 false,取反肯定是 true,所以只要这参数是 true 就会进行一波操作。

我看了下默认是 false。

图片

同时我也上网查了下 rewriteBatchedStatements 参数,好家伙,好像有用!我直接将 jdbcurl 加上了这个参数:

图片

然后继续跑了下 mybatis-plus 自带的 saveBatch,果然性能大大提高,跟拼接 SQL 差不多!

图片

顺带我也跑了下 JDBC 的 executeBatch ,果然也提高了。

图片

然后我继续 debug ,来探探 rewriteBatchedStatements 究竟是怎么 rewrite 的!

如果这个参数是 true,则会执行下面的方法且直接返回:

图片

看下 executeBatchedInserts 究竟干了什么:

图片

看到上面我圈出来的代码没,好像已经有点感觉了,继续往下 debug。

果然!sql 语句被 rewrite了:

图片

对插入而言,所谓的 rewrite 其实就是将一批插入拼接成 insert into xxx values (a),(b),(c)...这样一条语句的形式然后执行,这样一来跟拼接 sql 的效果是一样的。

那为什么默认不给这个参数设置为 true 呢?

原来是这样的:

  1. 如果批量语句中的某些语句失败,则默认重写会导致所有语句都失败。
  2. 批量语句的某些语句参数不一样,则默认重写会使得查询缓存未命中。

看起来影响不大,所以我给我的项目设置上了这个参数!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

入职华为外包一个月后,我离职向“北上广深”流浪了...

这次来聊一个大家可能也比较关心的问题&#xff0c;那就是就业城市选择的问题。而谈到这个问题&#xff0c;就不可避免地会谈到一些关于&#xff1a;机会&#xff1f;技术氛围&#xff1f;跳槽&#xff1f;薪资水平&#xff1f;等等一系列问题。 正好&#xff0c;这也是大家所…

【react全家桶】react-router

本人大二学生一枚&#xff0c;热爱前端&#xff0c;欢迎来交流学习哦&#xff0c;一起来学习吧。 <专栏推荐> &#x1f525;&#xff1a;js专栏 &#x1f525;&#xff1a;vue专栏 &#x1f525;&#xff1a;react专栏 文章目录 11 【react-router】1.准备1.1 SPA1.2 …

搞懂分布式RPC开源框架-gRPC

搞懂分布式RPC开源框架-gRPC rpc解决了什么问题&#xff0c;与消息队列应用场景比较 rpc(远程调用方法):请求回应 socket网络问题 ------> 消除端到端交互问题 业务场景&#xff1a; rpc&#xff1a;同步地处理 消息队列&#xff1a;不紧迫的非必要的 异步解决问题 &#x…

【回眸】又是一年毕业季,怎么利用ChatGPT 4.0 优化毕业论文?

目录 【回眸】又是一年毕业季&#xff0c;怎么利用ChatGPT 4.0 优化毕业论文&#xff1f; 前言 ChatGPT4.0降重提示词&#xff08;3.5表现略逊色一些&#xff0c;不过也可以用这个来作为提示词&#xff09; 举个例子 降重前的原文 构思提示词 确定提问词 选用合适的翻译…

CloudCompare如何进行二次开发?

文章目录 0.引言1.界面设计2.功能实现3.结果展示 0.引言 CloudCompare源代码编译成功后&#xff0c;即可进行二次开发&#xff0c;可以通过修改源码或者制作插件&#xff08;插件开发详见&#xff1a;CloudCompare如何进行二次开发之插件开发&#xff1f;&#xff09;实现二次开…

SpringBoot配置文件(properties与yml详解)

目录 一&#xff0c;SpringBoot配置文件 1&#xff0c;配置文件的作用 2&#xff0c;配置文件的格式 二&#xff0c;properties 配置文件说明 1&#xff0c;properties 基本语法 2&#xff0c;读取配置文件 3&#xff0c;properties 的缺点 三&#xff0c;yml配置文件说…

掌握Linux指令和权限:一个入门教程

目录 一.Linux基本指令1.ls指令2.pwd指令3.cd指令4.touch指令5.mkair指令6.rmdir和rm指令 一.Linux基本指令 1.ls指令 语法格式:ls [选项][目录或者文件] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其…

Chrome扩展开发指南

前言 Chrome 扩展&#xff08;通常也叫插件&#xff09;也是软件程序&#xff0c;使用 Web&#xff08;HTML, CSS, and JavaScript&#xff09;技术栈开发。允许用户自定义 Chrome 浏览体验。开发者可以通过增加特效或功能来优化体验。例如&#xff1a;效率工具、信息聚合等等。…

数据结构考研版——KMP算法

一、 我们先看下面这个算法当比较到不匹配的时候 模式串后移一位&#xff0c;并且让比较指针回去&#xff0c;这就叫做指针的回溯&#xff0c;回溯就是造成这个简单算法效率比较低的原因 这种是朴素模式匹配算法&#xff0c;时间复杂度比较高 int index(Str str,Str substr…

MAVEN安装与配置

文章目录 一、安装MAVEN二、在IDEA中进行配置 一、安装MAVEN 打开MAVEN官网下载&#xff1a;https://maven.apache.org/download.cgi 选择这两个进行下载&#xff0c;然后直接解压缩到指定的安装目录即可。 配置环境变量 1&#xff09;MAVEN_HOME设置为maven的安装目录 2&…

指令段间及文件间参数调用过程(64位 Intel架构)

指令段间及文件间参数调用过程&#xff08;64位 Intel架构&#xff09; 文章目录 指令段间及文件间参数调用过程&#xff08;64位 Intel架构&#xff09;一. 指令段间的参数调用过程1.1 推论1.2 验证 二. 文件间的参数调用过程2.1 推论2.2 验证 三. 指令解释相关补充 一. 指令段…

基于html+css的图片展示24

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

docker安装rocketMQ

1、安装jkd1.8 docker pull java:8 或者 docker pull openjdk:8 查看已安装的镜像&#xff1a; docker images 运行jdk命令 docker run -d -it --name java-8 java:8 进入JDK 容器 docker exec -it java-8 /bin/bash 查看java版本&#xff0c;进入java-8容器后输入 &#x…

金融数字新型基础设施创新开放联合体今日成立

4月18日&#xff0c;“金融数字新型基础设施创新开放联合体”&#xff08;以下简称&#xff1a;联合体&#xff09;在上海成立。联合体由上海银行、复旦大学金融科技研究院、中电金信共同发起&#xff0c;首批成员单位汇聚产业链与供给侧的中坚力量&#xff1a;国泰君安证券、太…

基于 BaiduAI 的人脸检测系统(PyQt5图形化界面实现)

文章目录 写在前面的话总体结构学生端教师端教务处系统 总结 写在前面的话 前几天有个小伙伴私我会不会做关于人脸检测与识别的小项目&#xff0c;奈何我现在主要是学习研究NLP了&#xff0c;所以关于CV的很多东西也有点力不从心&#xff0c;突然想起来去年我的毕业设计就是做…

【Vue】学习笔记-内置指令/自定义指令

内置指令 自定义指令 内置指令v-text 指令v-html指令v-cloak指令v-once指令v-pre指令 自定义指令 内置指令 我们学过的指令&#xff1a; v-bind : 单向绑定解析表达式, 可简写为 :xxx v-model : 双向数据绑定 v-for : 遍历数组/对象/字符串 v-on : 绑定事件监听, 可简写为 v…

遍历思路与子问题思路:详解二叉树的基本操作

二叉树的结构定义&#xff1a; public class BinaryTree {//内部类 表示一个结点static class TreeNode {TreeNode left; //左子树TreeNode right; //右子树char value; //结点值TreeNode(char value) {this.value value;}}public TreeNode root; //根节点... } …

云原生时代下,应用全生命周期管理之道

引言 过去 10 年间&#xff0c;云计算已经从单一的 IT 服务演变成为新一代的软件架构范式&#xff0c;进而赋能企业管理和生产模式的创新。云计算也经历了从“资源上云”到“深度用云”的发展阶段。 在云原生时代&#xff0c;应用全生命周期管理之道成为企业关注的一个焦点。在…

蓝牙耳机什么品牌的音质好?300左右音质最好的蓝牙耳机推荐

随着蓝牙技术的发展&#xff0c;蓝牙耳机品牌也越来越多。要说什么品牌的音质好&#xff1f;首先还是要根据自己的预算出发。在此&#xff0c;我来给大家推荐几款300左右音质最好的蓝牙耳机&#xff0c;可以当个参考。 一、南卡小音舱Lite2蓝牙耳机 参考价&#xff1a;239 发…

Self-supervised learning of a facial attribute embedding from video

Self-supervised learning of a facial attribute embedding from video 译题&#xff1a;视频中人脸属性嵌入的自监督学习 论文题目Self-supervised learning of a facial attribute embedding from video译题视频中人脸属性嵌入的自监督学习时间2018年开源代码地址https://…