Mysql 批量插入四種方式效率對比

Mysql 批量插入四種方式效率對比

環境信息

mysql-5.7.12

mac pro

idea(分配最大內存2g)

測試數據

pom 依賴

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

數據庫

CREATE TABLE `people` (
  `id` bigint(8) NOT NULL AUTO_INCREMENT,
  `first_name` varchar(50) NOT NULL DEFAULT '',
  `last_name` varchar(50) NOT 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
        <trim prefix="set" suffixOverrides=",">
            <trim prefix="first_name = case" suffix="end,">
                <foreach collection="list" item="i" index="index">
                    <if test="i.firstName!=null">
                        when id=#{i.id} then #{i.firstName}
                    </if>
                </foreach>
            </trim>
            <trim prefix="last_name = case" suffix="end,">
                <foreach collection="list" item="i" index="index">
                    <if test="i.lastName!=null">
                        when id=#{i.id} then #{i.lastName}
                    </if>
                </foreach>
            </trim>
        </trim>
        where id in
        <foreach collection="list" index="index" item="item" separator="," open="(" close=")">
            #{item.id,jdbcType=BIGINT}
        </foreach>
    </update>

第三種 replace into

<!-- 批量更新第三種方法,通過 replace into  -->
<update id="updateBatch3" parameterType="java.util.List">
    replace into people
    (id,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})
        </foreach>
        ON DUPLICATE KEY UPDATE
        id=values(id),first_name=values(first_name),last_name=values(last_name)
    </update>

測試代碼

/**
 * 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);
}
    @Test
    void updateBatch() {
        List<People> list = new ArrayList<>();
        int loop = 100;
        int count = 100000;
        Long maxCost = 0L;//最長耗時
        Long minCost = Long.valueOf(Integer.MAX_VALUE);//最短耗時
        System.out.println("開始");
        Long startTime = System.currentTimeMillis();

        for (int j = 0; j < count; j++) {
            People people = new People();
            people.setId(ThreadLocalRandom.current().nextLong(0, 100000));
            people.setFirstName(UUID.randomUUID().toString());
            people.setLastName(UUID.randomUUID().toString());
            list.add(people);
        }

        for (int i = 0; i < loop; i++) {
            Long curStartTime = System.currentTimeMillis();
            peopleDAO.updateBatch4(list);
            Long curCostTime = System.currentTimeMillis() - curStartTime;
            if (maxCost < curCostTime) {
                maxCost = curCostTime;
            }
            if (minCost > curCostTime) {
                minCost = curCostTime;
            }
            System.out.println("耗時-" + (System.currentTimeMillis() - curStartTime));
        }
        System.out.println("結束");
        System.out.println("平均-" + (System.currentTimeMillis() - startTime) / loop + "ms");
        System.out.println("最小-" + minCost + "ms");
        System.out.println("最大-" + maxCost + "ms");
    }

效率比較

數據量 for case when replace into ON DUPLICATE KEY UPDATE
500 100次
平均-225ms
最小-110ms
最大-907ms
100次
平均-85ms
最小-31ms
最大-1118ms
100次
平均-47ms
最小-23ms
最大-649ms
100次
平均-50ms
最小-21ms最大-933ms
1000 100次
平均-371ms
最小-276ms
最大-1178ms
100次
平均-142ms
最小-83ms
最大-877ms
100次
平均-64ms
最小-25ms
最大-658ms
100次
平均-63ms
最小-23ms
最大-649ms
5000 100次
平均-1744ms
最小-1296ms
最大-3906ms
100次
平均-3657ms
最小-2606ms
最大-6437ms
100次
平均-286ms
最小-126ms
最大-1105ms
100次
平均-300ms
最小-131ms
最大-1490ms
10000 10
平均-3444ms
最小-2433ms
最大-5688ms
10
平均-12898ms最小-10929ms最大-14207ms
100次
平均-365ms
最小-267ms
最大-1409ms
100次
平均-335ms
最小-258ms
最大-1475ms
50000 10
平均-17761ms
最小-11305ms
最大-24575ms
卡死不動 100次
平均-1810ms
最小-1372ms
最大-3705ms
100次
平均-1923ms
最小-1323ms
最大-5008ms
100000 10
平均-31137ms
最小-27493ms
最大-34235ms
卡死不動 100次
平均-3249ms
最小-2713ms
最大-5582ms
100次
平均-3079ms
最小-2781ms
最大-6199ms

總結

sql語句for循環效率其實相當高的,因爲它僅僅有一個循環體,只不過最後update語句比較多,量大了就有可能造成sql阻塞。

case when雖然最後只會有一條更新語句,但是xml中的循環體有點多,每一個case when 都要循環一遍list集合,所以大批量拼sql的時候會比較慢,所以效率問題嚴重。使用的時候建議分批插入。

duplicate key update可以看出來是最快的,但是一般大公司都禁用,公司一般都禁止使用replace into和INSERT INTO … ON DUPLICATE KEY UPDATE,這種sql有可能會造成數據丟失和主從上表的自增id值不一致。而且用這個更新時,記得一定要加上id,而且values()括號裏面放的是數據庫字段,不是java對象的屬性字段

參考文章:

mybatis批量更新數據三種方法效率對比 https://blog.csdn.net/q957967519/article/details/88669552

MySql中4種批量更新的方法 https://blog.csdn.net/weixin_42290280/article/details/89384741

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章