• mysql批量插入性能优化:executeBatch如何通过rewriteBatchedStatements参数逆袭



    前言

    前面关于mybatis-plus的文章中提到过内置的批量插入方法saveBatch并不是真正的批量写入,而是通过executeBatch分批提交。所以我们通过sql注入器注入InsertBatchSomeColumn方法实现了insert的多值插入,提升了批量插入的性能。但其实还有更简单的优化方式,只通过添加一个参数,就能让采用executeBatch批量插入数据的性能实现逆袭。

    这就是今天给大家介绍的rewriteBatchedStatements参数。


    一、实战演示

    项目工程依然采用之前mybatis-plus系列文章中的工厂。
    这里我们通过在不添加rewriteBatchedStatements参数的前后采用executeBatch批量执行插入1万数据,并与InsertBatchSomeColumn方法进行对比。

    1、单元测试

        @Test
        public void testBatchInsert() {
            System.out.println("----- batch insert method test ------");
            long startTime = System.currentTimeMillis();
            List<User> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                User user = new User();
                user.setName("test");
                user.setAge(13);
                user.setEmail("101@qq.com");
                list.add(user);
            }
            userService.saveBatch(list);
            System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    saveBatch方法默认情况下,每次提交1000条sql。
    saveBatch方法的底层实现是通过executeBatch批量执行sql

    default boolean saveBatch(Collection<T> entityList) {
        return this.saveBatch(entityList, 1000);
    }
    
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
         String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
         return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
             sqlSession.insert(sqlStatement, entity);
         });
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、不添加rewriteBatchedStatements参数

    属性配置:

    spring.datasource.url = jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
    spring.datasource.username = root
    spring.datasource.password = 123456
    
    • 1
    • 2
    • 3

    测试结果:
    在这里插入图片描述
    说明是一条insert语句插入一条记录。
    在这里插入图片描述
    插入10000条数据,耗时49646ms

    3、添加rewriteBatchedStatements参数

    在mysql的数据库连接参数中添加rewriteBatchedStatements=true

    spring.datasource.url = jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&rewriteBatchedStatements=true
    spring.datasource.username = root
    spring.datasource.password = 123456
    
    • 1
    • 2
    • 3

    执行结果:
    在这里插入图片描述
    添加rewriteBatchedStatements=true后,executeBatch批量提交到mysql的sql语句还是一条insert语句插入一条记录
    插入10000条数据耗时1289ms,批量插入的效率得到大幅提升。
    在这里插入图片描述

    4、采用InsertBatchSomeColumn方法

    这里我们只需要将UserServiceImpl中采用InsertBatchSomeColumn重写的saveBatch方法的注释放开即可。

    @Service
    @Slf4j
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
        @Resource
        private UserMapper userMapper;
    
    
        /**
         *
         * @param entityList
         * @param batchSize
         * @return
         */
        @Override
        @Transactional(rollbackFor = {Exception.class})
        public boolean saveBatch(Collection<User> entityList, int batchSize) {
            try {
                int size = entityList.size();
                int idxLimit = Math.min(batchSize, size);
                int i = 1;
                //保存单批提交的数据集合
                List<User> oneBatchList = new ArrayList<>();
                for(Iterator<User> var7 = entityList.iterator(); var7.hasNext(); ++i) {
                    User element = var7.next();
                    oneBatchList.add(element);
                    if (i == idxLimit) {
                        userMapper.insertBatchSomeColumn(oneBatchList);
                        //每次提交后需要清空集合数据
                        oneBatchList.clear();
                        idxLimit = Math.min(idxLimit + batchSize, size);
                    }
                }
            }catch (Exception e){
                log.error("saveBatch fail",e);
                return false;
            }
            return  true;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    执行单元测试:
    可以看到,采用insertBatchSomeColumn方法进行的批量插入是采用了insert的多值插入,一条insert语句插入多条记录。这里每批插入1000条记录。
    最终,插入10000条记录只话费了663ms

    insertBatchSomeColumn方法由于底层并不是走的executeBatch批量提交sql,所以性能并不会受rewriteBatchedStatements参数的影响。
    在这里插入图片描述在这里插入图片描述

    二、官方文档

    Mysql官方文档:rewriteBatchedStatements
    在这里插入图片描述
    核心:
    prepared statements for INSERT into multi-value inserts when executeBatch() is called

    rewriteBatchedStatements选项默认是关闭的,3.1.13以后的mysql连接驱动都支持该配置。
    如果开启该配置rewriteBatchedStatements=true,在调用 executeBatch() 批量执行 INSERT语句时,mysql内部会自动将批量提交的sql重写为insert多值插入再执行。


    总结

    本文主要介绍在采用executeBatch进行mysql批量数据插入时,通过在mysql连接信息中添加rewriteBatchedStatements=true使得执行效率大幅提升。
    1、批量sql重写开关参数rewriteBatchedStatements默认是关闭的,mysql连接驱动器版本3.1.13以后支持该配置。
    2、其底层原理是:将通过executeBatch方法批量提交到mysql服务端的sql重写为insert多值插入再执行
    3、通过测试发现,开启rewriteBatchedStatements后,采用executeBatch方法批量插入的性能已经接近InsertBatchSomeColumn真实insert多值批量写入。

  • 相关阅读:
    Speech | .flac文件转换为.wav文件,并进行重采样(Python脚本)
    机器学习——逻辑回归
    C语言达到什么水平才能从事单片机工作
    2021年软件测试面试题大全
    UDP和TCP协议发送接收数据
    IDEA2023.2.1取消空包隐藏,切换包结构(Compact Middle Packages)
    【无标题】
    Android开发学习日记--利用元数据给应用设置快捷方式(支付宝为例)
    如何清除运行 Docker 容器的日志
    ThinkingPython | 关于Programing 和 Debugging的认识
  • 原文地址:https://blog.csdn.net/w1014074794/article/details/125858863