偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

批量update實現(xiàn)方案全面解析與最佳實踐,帶你掌握到底怎么批量更新最快、性能最高

數(shù)據(jù)庫 其他數(shù)據(jù)庫
批量更新方案的選擇需要綜合考慮數(shù)據(jù)庫類型、數(shù)據(jù)量大小、系統(tǒng)架構(gòu)要求和團隊技術(shù)棧等因素。對于大多數(shù)MySQL應(yīng)用場景,ON DUPLICATE KEY UPDATE?方案提供了最佳的性能和可維護性平衡。

1.概述

在當(dāng)今應(yīng)用開發(fā)中,數(shù)據(jù)操作是底層基礎(chǔ),批量更新是實際開發(fā)中一個常見的操作,同時也是一個性能瓶頸點。有多種批量更新的實現(xiàn)方式,但不同的方案在性能、可維護性和數(shù)據(jù)庫兼容性等方面差異顯著。本文將基于MyBatis全面剖析各種批量更新方案的實現(xiàn)原理、性能表現(xiàn)和適用場景,幫助開發(fā)者做出合理的技術(shù)選型,從而實現(xiàn)性能最高的更新。

2.準(zhǔn)備工作

這里我們還是以用戶表tb_user為示例,并且基于上面總結(jié)快速插入了500多萬條數(shù)據(jù):

CREATE TABLE `tb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `user_no` varchar(255) NOT NULL COMMENT '編號',
  `name` varchar(255) DEFAULT NULL COMMENT '昵稱',
  `email` varchar(255) DEFAULT NULL COMMENT '郵箱',
  `phone` varchar(255) NOT NULL COMMENT '手機號',
  `gender` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性別  0:男生   1:女生',
  `birthday` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '出生日期',
  `is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '刪除標(biāo)志 0:否  1:是',
  `create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
  `update_time` datetime DEFAULT NULL COMMENT '更新時間',
  `creator` bigint(20) DEFAULT NULL COMMENT '創(chuàng)建人',
  `updater` bigint(20) DEFAULT NULL COMMENT '更新人',
  `address` varchar(1024) DEFAULT NULL COMMENT '地址',
  `role_id` varchar(100) DEFAULT NULL COMMENT '角色id',
  `hobby` varchar(255) DEFAULT NULL COMMENT '愛好',
  `remark` varchar(255) DEFAULT NULL COMMENT '個人說明',
  `org_id` bigint(20) NOT NULL COMMENT '公司id',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_user_no` (`user_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5201026 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

當(dāng)同樣更新100條數(shù)據(jù)時,小表(幾千條)和大表(幾百萬條)使用相同的批量更新方式,執(zhí)行效率會有差異,差異程度取決于多個因素

效率不會完全相同,但差異可能不明顯,主要因為:

  • 數(shù)據(jù)定位成本:大表可能需要更多I/O來定位記錄
  • 索引結(jié)構(gòu)差異:大表的索引層級可能更深
  • 內(nèi)存緩存影響:小表更可能完全緩存在內(nèi)存中

所以我這里為了更能突出區(qū)別不同批量更新方案的執(zhí)行效率,選擇了對大表進行批量更新10000條數(shù)據(jù)來示例。當(dāng)然了執(zhí)行效率還與MySQL服務(wù)的配置有關(guān),配置2核2G和4核8G肯定是不一樣的。

3.批量更新實現(xiàn)方案

這里我先查出10000條數(shù)據(jù),更新user的name,gender,address等字段

public List<User> listUsers() {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(User::getId, User::getName);
        queryWrapper.ge(User::getId, 10000L).lt(User::getId, 20000L);
        List<User> users = userDAO.selectList(queryWrapper);
        users.forEach(user -> {
            user.setName(user.getName() + "1");
            user.setAddress("杭州" + user.getId());
            user.setGender(user.getId() % 2 == 0 ? 1 : 0);
            user.setUpdateTime(new Date());
        });
        return users;
    }

3.1 循環(huán)單條更新

這種方式最簡單,直接看代碼:

@Test
  public void testBatchUpdateByFor() {
      List<User> users = listUsers();
      long start = System.currentTimeMillis();
      users.forEach(user -> {
          userDAO.updateById(user);
      });
      long end = System.currentTimeMillis();
      System.out.println("執(zhí)行時長:" + (end - start) + "ms");
  }

執(zhí)行SQL部分如下:

c.p.b.e.mybatis.dao.UserDAO.updateById   : ==>  Preparing: UPDATE tb_user SET gender=?, address=?, name=?, update_time=?, updater=? WHERE id=?
c.p.b.e.mybatis.dao.UserDAO.updateById   : ==> Parameters: 1(Integer), 杭州19998(String), 羅百夜1(String), 2025-07-08 11:08:23.588(Timestamp), null, 19998(Long)
c.p.b.e.mybatis.dao.UserDAO.updateById   : <==    Updates: 1
 c.p.b.e.mybatis.dao.UserDAO.updateById   : ==>  Preparing: UPDATE tb_user SET gender=?, address=?, name=?, update_time=?, updater=? WHERE id=?
 c.p.b.e.mybatis.dao.UserDAO.updateById   : ==> Parameters: 0(Integer), 杭州19999(String), 張七土1(String), 2025-07-08 11:08:23.588(Timestamp), null, 19999(Long)
c.p.b.e.mybatis.dao.UserDAO.updateById   : <==    Updates: 1

可以看出是一條一條提交執(zhí)行的。

執(zhí)行時長:3846ms

這種方式產(chǎn)生N條獨立SQL語句,網(wǎng)絡(luò)IO次數(shù)與數(shù)據(jù)量成正比,性能很差,在平時開發(fā)中幾乎不能使用,當(dāng)然如果是操作小表小批量數(shù)據(jù),也問題不大,但最好別這么寫,顯得代碼水平不行,同時這種方式也是代碼性能提升方式經(jīng)常提到一大問題點:for循環(huán)里面單條操作SQL語句,這種方式寫了就有性能問題~~~

3.2 foreach多條SQL

這種方式需要通過XML寫SQL語句實現(xiàn)

public interface UserDAO extends BaseMapperX<User> {
    int batchUpdateByForeach(@Param("userList") List<User> userList);
}

XML配置如下:

<update id="batchUpdateByForeach">
        <foreach collection="userList" item="u" separator=";">
            UPDATE tb_user
            SET
            update_time = now()
            <if test="u.name != null">
                ,name = #{u.name}
            </if>
            <if test="u.address != null">
                ,address = #{u.address}
            </if>
            <if test="u.gender != null">
                ,gender = #{u.gender}
            </if>
            WHERE id = #{u.id}
        </foreach>
    </update>

測試代碼:

@Test
  public void testBatchUpdateByForeach() {
      List<User> users = listUsers();
      long start = System.currentTimeMillis();
      // 分批處理
      List<List<User>> splitList = CollUtil.split(users, 500);
      splitList.forEach(userList -> {
          userDAO.batchUpdateByForeach(userList);
      });
      long end = System.currentTimeMillis();
      System.out.println("執(zhí)行時長:" + (end - start) + "ms");
  }

這里我只給出了3條數(shù)據(jù)的更新SQL,500條全給出來太多了。

c.p.b.e.m.d.U.batchUpdateByForeach       : ==>  Preparing: UPDATE tb_user SET update_time = now() ,name = ? ,address = ? ,gender = ? WHERE id = ? ; UPDATE tb_user SET update_time = now() ,name = ? ,address = ? ,gender = ? WHERE id = ? ; UPDATE tb_user SET update_time = now() ,name = ? ,address = ? ,gender = ? WHERE id = ? ; 
c.p.b.e.m.d.U.batchUpdateByForeach       : ==> Parameters: 王十金1111(String), 杭州13000(String), 1(Integer), 13000(Long), 楊一月1111(String), 杭州13001(String), 0(Integer), 13001(Long), 周六云1111(String), 杭州13002(String), 1(Integer), 13002(Long)
2025-07-08T13:55:41.618+08:00 DEBUG 53878 --- [plasticene-boot-mybatis-example] [           main] c.p.b.e.m.d.U.batchUpdateByForeach       : <==    Updates: 1

可以看出是單次請求包含多條SQL語句,但本質(zhì)上每條數(shù)據(jù)都是單獨執(zhí)行更新的

執(zhí)行時長:1417ms

3.3 CASE WHEN表達(dá)式

直接看XML配置里面寫的SQL語句:

<update id="batchUpdateByCaseWhen">
        UPDATE tb_user
        SET
        update_time=now(),
        name = CASE
        <foreach collection="userList" item="item">
            WHEN id = #{item.id} AND #{item.name} IS NOT NULL THEN #{item.name}
        </foreach>
        ELSE name
        END,
        address = CASE
        <foreach collection="userList" item="item">
            WHEN id = #{item.id} AND #{item.address} IS NOT NULL THEN #{item.address}
        </foreach>
        ELSE address
        END,
        gender = CASE
        <foreach collection="userList" item="item">
            WHEN id = #{item.id} AND #{item.gender} IS NOT NULL THEN #{item.gender}
        </foreach>
        ELSE gender
        END
        WHERE id IN
        <foreach collection="userList" item="item" open="(" separator="," close=")">
            #{item.id}
        </foreach>
    </update>

測試代碼:

@Test
  public void testBatchUpdateByCaseWhen() {
      List<User> users = listUsers();
      long start = System.currentTimeMillis();
      // 分批處理
      List<List<User>> splitList = CollUtil.split(users, 500);
      for (List<User> userList : splitList) {
          userDAO.batchUpdateByCaseWhen(userList);
      }
      long end = System.currentTimeMillis();
      System.out.println("執(zhí)行時長:" + (end - start) + "ms");
  }

這里就不給出控制臺的輸出的SQL語句了,太長了,大家自行執(zhí)行查看

執(zhí)行時長:988ms

真正的單SQL批量操作,性能很好,但要注意防止SQL語句長度超過限制。

3.4 ON DUPLICATE KEY UPDATE

ON DUPLICATE KEY UPDATE是MySQL特有語法,批量插入,遇到主鍵/唯一鍵沖突時轉(zhuǎn)為更新。

<insert id="batchUpdateOnDuplicate">
    INSERT INTO tb_user(user_no, name, phone, address, gender, org_id) VALUES
    <foreach collection="userList" item="item" separator=",">
        (#{item.userNo}, #{item.name}, #{item.phone}, #{item.address}, #{item.gender}, #{item.orgId})
    </foreach>
    ON DUPLICATE KEY UPDATE
    name=VALUES(name), org_id=VALUES(org_id)
</insert>

測試代碼:

@Test
  public void testBatchUpdateOnDuplicate() {
      List<User> users = listUsers();
      long start = System.currentTimeMillis();
      // 分批處理
      List<List<User>> splitList = CollUtil.split(users, 500);
      for (List<User> userList : splitList) {
          userDAO.batchUpdateOnDuplicate(userList);
      }
      long end = System.currentTimeMillis();
      System.out.println("執(zhí)行時長:" + (end - start) + "ms");
  }

執(zhí)行時長:1080ms

3.5 REPLACE INTO

replace into與on duplicate key update在一定程度上都能實現(xiàn)無記錄時插入,有記錄時更新。其判斷都是根據(jù)主鍵/唯一鍵是否存在,但是replace into實現(xiàn)更新的方式是先刪除在插入,這就會產(chǎn)生兩個binlog,可能導(dǎo)致消費binlog出問題,同時這種更新如果是唯一鍵沖突,那么先刪后插會導(dǎo)致主鍵變了,如果之前的主鍵id有在其他表關(guān)聯(lián)使用,這種更新是很危險的。

<insert id="batchUpdateReplace">
    REPLACE INTO tb_user(user_no, name, phone, address, gender, org_id) VALUES
    <foreach collection="userList" item="item" separator=",">
        (#{item.userNo}, #{item.name}, #{item.phone}, #{item.address}, #{item.gender}, #{item.orgId})
    </foreach>
</insert>

測試代碼:

@Test
  public void testBatchUpdateReplace() {
      List<User> users = listUsers();
      long start = System.currentTimeMillis();
      // 分批處理
      List<List<User>> splitList = CollUtil.split(users, 500);
      for (List<User> userList : splitList) {
          userDAO.batchUpdateReplace(userList);
      }
      long end = System.currentTimeMillis();
      System.out.println("執(zhí)行時長:" + (end - start) + "ms");
  }

執(zhí)行時長:6705ms

3.6 通過MyBatis-Plus批量更新

直接看代碼:

@Test
    public void testBatchUpdateByMybatisPlus() {
        List<User> users = listUsers();
        long start = System.currentTimeMillis();
        userDAO.updateById(users, 500);
        long end = System.currentTimeMillis();
        System.out.println("執(zhí)行時長:" + (end - start) + "ms");
    }

執(zhí)行時長:1730ms

4.性能對比表格

方案

1萬條耗時

網(wǎng)絡(luò)IO次數(shù)

SQL解析次數(shù)

適用數(shù)據(jù)量

數(shù)據(jù)庫兼容性

for循環(huán)單條更新

3.-4.s

N

N

<100

全兼容

foreach多條SQL

1-2s

1

N

100-5000

需配置

mybaits-plus

1-2s

1

1

100-5000

全兼容

CASE WHEN

0.5-1s

1

1

>1000

全兼容

ON DUPLICATE KEY UPDATE

0.5-1s

1

1

>1000

MySQL only

replace into

4-7s

1

N

100-3000

全兼容

除了for循環(huán)單條更新不推薦之外,其他方式我個人感覺都可以選擇,可以根據(jù)具體場景選擇具體方式。追求極致性能首選case when

如果存在做更新,沒有就插入實現(xiàn)方案首選ON DUPLICATE KEY UPDATE,因為replace into操作可能存在問題,具體看上面敘述,當(dāng)然了MyBatis-Plus提供了saveOrUpdateBatch可以操作小批量數(shù)據(jù),因為它底層是for循環(huán)單條操作實現(xiàn)的,比較慢。

5.總結(jié)

批量更新方案的選擇需要綜合考慮數(shù)據(jù)庫類型、數(shù)據(jù)量大小、系統(tǒng)架構(gòu)要求和團隊技術(shù)棧等因素。對于大多數(shù)MySQL應(yīng)用場景,ON DUPLICATE KEY UPDATE方案提供了最佳的性能和可維護性平衡。而在需要多數(shù)據(jù)庫支持的場景中,CASE WHEN表達(dá)式則是更為通用的選擇。無論采用哪種方案,都應(yīng)該結(jié)合分批次處理、連接參數(shù)優(yōu)化和適當(dāng)?shù)谋O(jiān)控手段,才能在實際生產(chǎn)環(huán)境中獲得理想的性能表現(xiàn)。

責(zé)任編輯:武曉燕 來源: Shepherd進階筆記
相關(guān)推薦

2025-05-23 07:05:03

2009-07-17 16:38:42

ibatis批量update

2025-04-30 05:00:00

批量運維系統(tǒng)

2010-11-02 10:52:15

批量清理文件

2009-09-25 11:34:54

Hibernate處理Hibernate批量

2010-02-23 09:33:39

Hibernate批量Hibernate批量

2022-04-14 10:10:59

Nginx開源Linux

2025-01-02 10:19:18

2009-09-24 09:45:23

Hibernate批量

2024-08-29 08:31:16

2024-09-25 08:22:06

2024-10-28 08:38:40

會員批量應(yīng)用

2024-08-13 08:30:13

2010-07-06 09:07:09

2023-09-13 08:00:00

JavaScript循環(huán)語句

2011-04-29 09:15:10

Ubuntu 11.0

2025-04-11 03:00:55

2015-01-26 14:41:30

數(shù)據(jù)中心遷移

2013-09-22 10:25:23

MySQLSQL性能優(yōu)化

2018-08-22 11:31:59

華為云
點贊
收藏

51CTO技術(shù)棧公眾號