SpringBoot中六種批量更新Mysql的方式效率對(duì)比分析
先上結(jié)論吧,有空可以自測(cè)一下,數(shù)據(jù)量大時(shí)運(yùn)行一次還時(shí)挺耗時(shí)的
效率比較
小數(shù)據(jù)量時(shí)6中批量更新效率不太明顯,根據(jù)項(xiàng)目選擇合適的即可,以1萬(wàn)條為準(zhǔn)做個(gè)效率比較,效率從高到低一次排名如下
replace into
和ON DUPLICATE KEY
效率最高mybatis-plus
有取巧嫌疑,因?yàn)槭欠峙扛?其他幾種都是一次更新- for循環(huán)憑借sql和JdbcTemplate相近,即使5萬(wàn)條,10萬(wàn)條效率也相近
case when
然而有時(shí)候我們只能選擇case when,因?yàn)?code>replace into和ON DUPLICATE KEY
公司不一定讓用,項(xiàng)目也不一定引入mybatis-plus,數(shù)據(jù)庫(kù)url中也不一定有allowMultiQueries=true參數(shù),算是一個(gè)兜底方案吧,不管用那種方式大數(shù)據(jù)量時(shí)都需要考慮分批
測(cè)試結(jié)構(gòu)
環(huán)境信息:mysql-8.0.35-winx64,本地win 10
依次為測(cè)試次數(shù)-平均耗時(shí)-最小耗時(shí)-最大耗時(shí),單位為毫秒
數(shù)據(jù)量 | for | case when | replace into | ON DUPLICATE KEY | mybatis-plus | JdbcTemplate |
---|---|---|---|---|---|---|
500 | 100-61-41-1202 | 100-66-57-426 | 100-16-10-282 | 100-15-10-293 | 100-73-52-564 | 100-87-59-1449 |
1000 | 100-131-94-2018 | 100-241-219-675 | 100-28-18-376 | 100-25-17-331 | 100-117-98-599 | 100-188-136-2397 |
5000 | 100-852-735-8297 | 100-11219-10365-13496 | 100-95-83-569 | 100-93-82-552 | 100-618-517-1415 | 100-1161-911-9334 |
10000 | 10-3957-2370-17304 | 10-45537-44465-48119 | 100-191-171-762 | 100-188-169-772 | 100-1309-1085-5021 | 100-3671-2563-31112 |
50000 | 10-50106-34568-130651 | 卡死不動(dòng) | 100-1026-919-1868 | 100-1062-945-1934 | 100-8062-6711-20841 | 100-48744-35482-191011 |
100000 | 10-160170-106223-264434 | 卡死不動(dòng) | 10-2551-2292-3688 | 10-2503-2173-3579 | 100-17205-14436-24881 | 10-169771-110522-343278 |
心得:
sql語(yǔ)句for循環(huán)效率其實(shí)相當(dāng)高的,因?yàn)樗鼉H僅有一個(gè)循環(huán)體,只不過(guò)最后update語(yǔ)句比較多,量大了就有可能造成sql阻塞,同時(shí)在mysql的url上需要加上allowMultiQueries=true參數(shù),即 jdbc:mysql://localhost:3306/mysqlTest?characterEncoding=utf-8&allowMultiQueries=true(公司項(xiàng)目不一定加,我們也不一定有權(quán)限加)。
case when雖然最后只會(huì)有一條更新語(yǔ)句,但是xml中的循環(huán)體有點(diǎn)多,每一個(gè)case when 都要循環(huán)一遍list集合,所以大批量拼sql的時(shí)候會(huì)比較慢,所以效率問(wèn)題嚴(yán)重。使用的時(shí)候建議分批插入(我們公司一直用的就是這種,但是必須分批)。
duplicate key update可以看出來(lái)是最快的,但是公司一般都禁止使用replace into和INSERT INTO … ON DUPLICATE KEY UPDATE,這種sql有可能會(huì)造成數(shù)據(jù)丟失和主從上表的自增id值不一致。而且用這個(gè)更新時(shí),記得一定要加上id,而且values()括號(hào)里面放的是數(shù)據(jù)庫(kù)字段,不是java對(duì)象的屬性字段
根據(jù)效率,安全方面綜合考慮,選擇適合的很重要。
數(shù)據(jù)庫(kù)
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
初始化測(cè)試數(shù)據(jù)
//初始化10w數(shù)據(jù) @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
<!-- 批量更新第一種方法,通過(guò)接收傳進(jìn)來(lái)的參數(shù)list進(jìn)行循環(huán)組裝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
<!-- 批量更新第二種方法,通過(guò) case when語(yǔ)句變相的進(jìn)行批量更新 --> <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
<!-- 批量更新第三種方法,通過(guò) 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
<!-- 批量更新第四種方法,通過(guò) 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>
第五種mybatis-plus提供的的批量更新
default boolean updateBatchById(Collection<T> entityList) { return this.updateBatchById(entityList, 1000); } boolean updateBatchById(Collection<T> entityList, int batchSize);
mybatis-plus提供的批量更新是分批批量更新,默認(rèn)每批1000條,可以指定分批的條數(shù),每批執(zhí)行完成后提交一下事務(wù),不加@Transactional可能會(huì)出現(xiàn)第一批更新成功了,第二批更新失敗了的情況.
第六種JdbcTemplate提供的批量更新
測(cè)試代碼
/** * PeopleDAO繼承基類(lèi) */ @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;//最長(zhǎng)耗時(shí) Long minCost = Long.valueOf(Integer.MAX_VALUE);//最短耗時(shí) 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.add(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); } }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot 如何通過(guò)集成 Flink CDC 來(lái)實(shí)時(shí)追蹤 MySql 數(shù)據(jù)變動(dòng)
- SpringBoot應(yīng)用是否存在MySQL連接泄漏問(wèn)題的排查方法
- IDEA中為SpringBoot項(xiàng)目接入MySQL數(shù)據(jù)庫(kù)的詳細(xì)指南
- SpringBoot+ MySQL多線(xiàn)程查詢(xún)與聯(lián)表查詢(xún)性能對(duì)比
- 基于SpringBoot+Mybatis實(shí)現(xiàn)Mysql分表
- SpringBoot項(xiàng)目整合達(dá)夢(mèng)數(shù)據(jù)庫(kù)詳解(MYSQL轉(zhuǎn)換達(dá)夢(mèng)數(shù)據(jù)庫(kù))
相關(guān)文章
Java 將文件轉(zhuǎn)為字節(jié)數(shù)組知識(shí)總結(jié)及實(shí)例詳解
這篇文章主要介紹了Java 將文件轉(zhuǎn)為字節(jié)數(shù)組實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2016-12-12Java中的MultipartFile接口和File類(lèi)解讀
本文主要介紹了Java中的File類(lèi)和Spring框架中的MultipartFile接口,File類(lèi)提供了對(duì)文件和目錄操作的方法,如創(chuàng)建、刪除、重命名、判斷文件是否存在等,MultipartFile接口用于處理文件上傳,提供了獲取上傳文件信息和保存上傳文件的方法2025-02-02Spring實(shí)戰(zhàn)之使用ClassPathResource加載xml資源示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之使用ClassPathResource加載xml資源,結(jié)合實(shí)例形式分析了Spring使用ClassPathResource加載xml資源的具體實(shí)現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2019-12-12Spring Boot攔截器Interceptor與過(guò)濾器Filter詳細(xì)教程(示例詳解)
本文詳細(xì)介紹了SpringBoot中的攔截器(Interceptor)和過(guò)濾器(Filter),包括它們的定義、作用范圍、使用場(chǎng)景、實(shí)現(xiàn)步驟、執(zhí)行順序、常見(jiàn)問(wèn)題及解決方案,感興趣的朋友跟隨小編一起看看吧2025-03-03JAVA時(shí)間存儲(chǔ)類(lèi)Period和Duration使用詳解
這篇文章主要為大家介紹了JAVA時(shí)間存儲(chǔ)類(lèi)Period和Duration使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09SpringLDAP連接LDAPS證書(shū)報(bào)錯(cuò)問(wèn)題及解決
這篇文章主要介紹了SpringLDAP連接LDAPS證書(shū)報(bào)錯(cuò)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05關(guān)于Feign調(diào)用服務(wù)Headers傳參問(wèn)題
這篇文章主要介紹了關(guān)于Feign調(diào)用服務(wù)Headers傳參問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03java input 調(diào)用手機(jī)相機(jī)和本地照片上傳圖片到服務(wù)器然后壓縮的方法
今天小編就為大家分享一篇java input 實(shí)現(xiàn)調(diào)用手機(jī)相機(jī)和本地照片上傳圖片到服務(wù)器然后壓縮的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08