开发者

SpringBoot中六种批量更新Mysql的方式效率对比分析

目录
  • 效率比较
  • 测试结构
  • 数据库
  • 初始化测试数据
  • 批量修改方案
    • 第一种 for
    • 第二种 case when
    • 第三种 replace into
    • 第四种 ON DUPLICATE KEY UPDATE
    • 第五种myBATis-plus提供的的批量更新
    • 第六种JdbcTemplate提供的批量更新
  • 总结

    先上结论吧,有空可以自测一下,数据www.devze.com量大时运行一次还时挺耗时的

    效率比较

    小数据量时6中批量更新效率不太明显,根据项目选择合适的即可,以1万条为准做个效率比较,效率从高到低一次排名如下

    1. replace intoON DUPLICATE KEY效率最高
    2. mybatis-plus 有取巧嫌疑,因为是分批批量更新,其他几种都是一次更新
    3. for循环凭借sql和JdbcTemplate相近,即使5万条,10万条效率也相近
    4. case when

    然而有时候我们只能选择case when,因为replace intoON DUPLICATE KEY公司不一定让用,项目也不一定引入mybatis-plus,数据库url中也不一定有allowMultiQueries=true参数,算是一个兜底方案吧,不管用那种方式大数据量时都需要考虑分批

    测试结构

    环境信息:mysql-8.0.35-winx64,本地win 10

    依次为测试次数-平均耗时-最小耗时-最大耗时,单位为毫秒

    数据量forcase whenreplace intoON DUPLICATE KEYmybatis-plusJdbcTemplate
    500100-61-41-1202100-66-57-426100-16-10-282100-15-10-293100-73-52-564100-87-59-1449
    1000100-131-94-2018100-241-219-675100-28-18-376100-25-17-331100-117-98-599100-188-136-2397
    5000100-852-735-8297100-11219-10365-13496100-95-83-569100-93-82-552100-618-517-1415100-1161-911-9334
    1000010-3957-2370-1730410-45537-44465-48119100-191-171-762100-188-169-772100-1309-1085-5021100-3671-2563-31112
    5000010-50106-34568-130651卡死不动100-1026-919-1868100-1062-945-1934100-8062-6711-20841100-48744-35482-191011
    10000010-160170-106223-264434卡死不动10-2551-2292-368810-2503-2173-3579100-17205-14436-2488110-169771-110522-343278

    心得:

    sql语句for循环效率其实相当高的,因为它仅仅有一个循环体,只不过最后update语句比较多,量大了就有可能造成sql阻塞,同时在mysql的url上需要加上allowMultiQueries=true参数,即 jdbc:mysql://localhost:3306/mysqlTest?characterEncoding=utf-8&allowMultiQueries=true(公司项目不一定加,我们也不一定有权限加)。

    case when虽然最后只会有一条更新语句,但是XML中的循环体有点多,每一个case when 都要循环一遍list集合,所以大批量拼sql的时候会比较慢,所以效率问题严重。使用的时候建议分批插入(我们公司一直用的就是这种,但是必须分批)。

    duplicate key update可以看出来是最快的,但是公司一般都禁止使用replace into和INSERT INTO … ON DUPLICATE KEY UPDATE,这种sql有可能会造成数据丢失和主从上表的自增id值不一致。而且用这个更新时,记得一定要加上id,而且values()括号里面放的是数据库字段,不是Java对象的属性字段

    根据效率,安全方面综合考虑,选择适合的很重要。

    数据库

    CREATE TABLE `people` (
      `id` bigint(8) NOT NULL AUTO_INCREMENT,
      `first_name` varchar(50) NOT NULL DEFAULT '',
      `last_name` varchar(50) NOTandroid NULL DEFAULT '',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
    

    初始化测试数据

    //初始化10w数据
    @Test
    void init10wData() {
        for (int i = 0; i < 100000; i++) {
            People people = new People();
            people.setFirstName(UUID.randomUUID().toString());
            people.setLastName(UUID.randomUUID().toString());
            peopleDAO.insert(people);
        }
    }
    

    批量修改方案

    第一种 for

    <!-- 批量更新第一种方法,通过接收传进来的参数list进行循环组装sql -->
    <update id="updateBatch" parameterType="java.util.List">
        <foreach collection="list" item="item" index="index" open="" close="" separator=";">
            update people
            <set>
                <if test="item.firstName != null">
                    first_name = #{item.firstName,jdbcType=VARCHAR},
                </if>
                <if test="item.lastName != null">
                    last_name = #{item.lastName,jdbcType=VARCHAR},
                </if>
            </set>
            where id = #{item.id,jdbcType=BIGINT}
        </foreach>
    </update>
    

    第二种 case when

    <!-- 批量更新第二种方法,通过 case when语句变相的进行批量更新 -->
    <update id="updateBatch2" parameterType="java.util.List">
        update people
        <set>
            <foreach collection="list" item="item">
                <if test="item.firstName != null">
                    first_name = case when id = #{item.id} then #{item.firstName} else first_name end,
                </if>
                <if test="item.lastName != null">
                    last_name = case when id = #{item.id} then #{item.lastName} else last_name end,
                </if>
            </foreach>
        </set>
        where id in
        <foreach collection="list" item="item" separator="," open="(" close=")">
            #{item.id}
        </foreach>
    </update>
    

    第三种 replace into

    <!-- 批量更新第三种方法,通过 replace into  -->
    <update id="updateBatch3" parameterType="java.util.List">
        replace into people
        (phpid,first_name,last_name) values
        <foreach collection="list" index="index" item="item" separator=",">
            (#{item.id},
            #{item.firstName},
            #{item.lastName})
        </foreach>
    </update>
    

    第四种 ON DUPLICATE KEY UPDATE

    <!-- 批量更新第四种方法,通过 duplicate key update  -->
    <update id="updateBatch4" parameterType="java.util.List">
        insert into people
        (id,first_name,last_name) values
        <foreach collection="list" index="index" item="item" separator=",">
            (#{item.id},
            #{item.firstName},
            #{item.lastName})js
        </foreach>
        ON DUPLICATE KEY UPDATE
        id=values(id),first_name=values(first_name),last_name=values(last_name)
    </update>
    

    第五种mybatis-plus提供的的批量更新

    default boolean updateBatchById(Collection<T> entityList) {
        return this.updateBatchById(entityList, 1000);
    }
    boolean updateBatchById(Collection<T> entityList, int batchSize);
    

    mybatis-plus提供的批量更新是分批批量更新,默认每批1000条,可以指定分批的条数,每批执行完成后提交一下事务,不加@Transactional可能会出现第一批更新成功了,第二批更新失败了的情况.

    第六种JdbcTemplate提供的批量更新

    测试代码

    /**
     * PeopleDAO继承基类
     */
    @Mapper
    @Repository
    public interface PeopleDAO extends MyBatisBaseDao<People, Long> {
    
        void updateBatch(@Param("list") List<People> list);
    
        void updateBatch2(List<People> list);
    
        void updateBatch3(List<People> list);
    
        void updateBatch4(List<People> list);
    }
    
    
    @SpringBootTest
    class PeopleMapperTest {
        @Resource
        PeopleMapper peopleMapper;
        @Resource
        PeopleService peopleService;
        @Resource
        JdbcTemplate jdbcTemplate;
    
        @Test
        void init10wData() {
            for (int i = 0; i < 100000; i++) {
                People people = new People();
                people.setFirstName(UUID.randomUUID().toString());
                people.setLastName(UUID.randomUUID().toString());
                peopleMapper.insert(people);
            }
        }
    
    
        @Test
        void updateBatch() {
            List<People> list = new ArrayList();
            int loop = 100;
            int count = 5000;
            Long maxCost = 0L;//最长耗时
            Long minCost = Long.valueOf(Integer.MAX_VALUE);//最短耗时
            for (int j = 0; j < count; j++) {
                People people = new People();
                people.setId(ThreadLocalRandom.current().nextInt(0, 100000));
                people.setFirstName(UUID.randomUUID().toString());
                people.setLastName(UUID.randomUUID().toString());
                list.ad编程客栈d(people);
            }
    
            Long startTime = System.currentTimeMillis();
            for (int i = 0; i < loop; i++) {
                Long curStartTime = System.currentTimeMillis();
                // peopleMapper.updateBatch4(list);
                // peopleService.updateBatchById(list);
                jdbcTemplateBatchUpdate(list);
                Long curCostTime = System.currentTimeMillis() - curStartTime;
                if (maxCost < curCostTime) {
                    maxCost = curCostTime;
                }
                if (minCost > curCostTime) {
                    minCost = curCostTime;
                }
            }
            System.out.println(loop + "-" + (System.currentTimeMillis() - startTime) / loop + "-" + minCost + "-" + maxCost );
        }
    
        private void jdbcTemplateBatchUpdate (List<People> list){
            String sql = "update people set first_name=?,last_name=? where id = ?";
            List<Object[]> params = list.stream().map(item -> new Object[]{item.getFirstName(), item.getLastName(), item.getId()}).collect(Collectors.toList());
            jdbcTemplate.batchUpdate(sql,params);
        }
    }
    

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜