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

本文主要是介绍SpringBoot中六种批量更新Mysql的方式效率对比分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringBoot中六种批量更新Mysql的方式效率对比分析》文章比较了MySQL大数据量批量更新的多种方法,指出REPLACEINTO和ONDUPLICATEKEY效率最高但存在数据风险,MyB...

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

效率比较

小数据量时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.adChina编程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);
    }
}

总结

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

这篇关于SpringBoot中六种批量更新Mysql的方式效率对比分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155392

相关文章

MySQL 主从复制部署及验证(示例详解)

《MySQL主从复制部署及验证(示例详解)》本文介绍MySQL主从复制部署步骤及学校管理数据库创建脚本,包含表结构设计、示例数据插入和查询语句,用于验证主从同步功能,感兴趣的朋友一起看看吧... 目录mysql 主从复制部署指南部署步骤1.环境准备2. 主服务器配置3. 创建复制用户4. 获取主服务器状态5

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原

Java docx4j高效处理Word文档的实战指南

《Javadocx4j高效处理Word文档的实战指南》对于需要在Java应用程序中生成、修改或处理Word文档的开发者来说,docx4j是一个强大而专业的选择,下面我们就来看看docx4j的具体使用... 目录引言一、环境准备与基础配置1.1 Maven依赖配置1.2 初始化测试类二、增强版文档操作示例2.

一文详解如何使用Java获取PDF页面信息

《一文详解如何使用Java获取PDF页面信息》了解PDF页面属性是我们在处理文档、内容提取、打印设置或页面重组等任务时不可或缺的一环,下面我们就来看看如何使用Java语言获取这些信息吧... 目录引言一、安装和引入PDF处理库引入依赖二、获取 PDF 页数三、获取页面尺寸(宽高)四、获取页面旋转角度五、判断

Spring Boot中的路径变量示例详解

《SpringBoot中的路径变量示例详解》SpringBoot中PathVariable通过@PathVariable注解实现URL参数与方法参数绑定,支持多参数接收、类型转换、可选参数、默认值及... 目录一. 基本用法与参数映射1.路径定义2.参数绑定&nhttp://www.chinasem.cnbs

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

MySQL深分页进行性能优化的常见方法

《MySQL深分页进行性能优化的常见方法》在Web应用中,分页查询是数据库操作中的常见需求,然而,在面对大型数据集时,深分页(deeppagination)却成为了性能优化的一个挑战,在本文中,我们将... 目录引言:深分页,真的只是“翻页慢”那么简单吗?一、背景介绍二、深分页的性能问题三、业务场景分析四、

MySQL 迁移至 Doris 最佳实践方案(最新整理)

《MySQL迁移至Doris最佳实践方案(最新整理)》本文将深入剖析三种经过实践验证的MySQL迁移至Doris的最佳方案,涵盖全量迁移、增量同步、混合迁移以及基于CDC(ChangeData... 目录一、China编程JDBC Catalog 联邦查询方案(适合跨库实时查询)1. 方案概述2. 环境要求3.

JAVA中安装多个JDK的方法

《JAVA中安装多个JDK的方法》文章介绍了在Windows系统上安装多个JDK版本的方法,包括下载、安装路径修改、环境变量配置(JAVA_HOME和Path),并说明如何通过调整JAVA_HOME在... 首先去oracle官网下载好两个版本不同的jdk(需要登录Oracle账号,没有可以免费注册)下载完

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命