0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何調(diào)優(yōu)MyBatis 25倍性能

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2023-05-30 09:56 ? 次閱讀
來源:yes的練級攻略
  • 粗略的實驗
  • 最后


最近在壓測一批接口,發(fā)現(xiàn)接口處理速度慢的有點超出預(yù)期,感覺很奇怪,后面定位發(fā)現(xiàn)是數(shù)據(jù)庫批量保存這塊很慢。

這個項目用的是 mybatis-plus,批量保存直接用的是 mybatis-plus 提供的 saveBatch。

我點進去看了下源碼,感覺有點不太對勁:

2ad185ce-fe8a-11ed-90ce-dac502259ad0.png

我繼續(xù)追蹤了下,從這個代碼來看,確實是 for 循環(huán)一條一條執(zhí)行了 sqlSession.insert,下面的 consumer 執(zhí)行的就是上面的 sqlSession.insert:

2adaae10-fe8a-11ed-90ce-dac502259ad0.png

然后累計一定數(shù)量后,一批 flush。

從這點來看,這個 saveBach 的性能肯定比直接一條一條 insert 快。

我直接進行一個粗略的實驗,簡單創(chuàng)建了一張表來對比一波!

粗略的實驗

1000條數(shù)據(jù),一條一條插入

@Test
voidMybatisPlusSaveOne(){
SqlSessionsqlSession=sqlSessionFactory.openSession();
try{
StopWatchstopWatch=newStopWatch();
stopWatch.start("mybatisplussaveone");
for(inti=0;i1000;i++){
OpenTestopenTest=newOpenTest();
openTest.setA("a"+i);
openTest.setB("b"+i);
openTest.setC("c"+i);
openTest.setD("d"+i);
openTest.setE("e"+i);
openTest.setF("f"+i);
openTest.setG("g"+i);
openTest.setH("h"+i);
openTest.setI("i"+i);
openTest.setJ("j"+i);
openTest.setK("k"+i);
//一條一條插入
openTestService.save(openTest);
}
sqlSession.commit();
stopWatch.stop();
log.info("mybatis plus save one:"+stopWatch.getTotalTimeMillis());
}finally{
sqlSession.close();
}
}
2ae2c12c-fe8a-11ed-90ce-dac502259ad0.png

可以看到,執(zhí)行一批 1000 條數(shù)的批量保存,耗費的時間是 121011 毫秒。

1000條數(shù)據(jù)用 mybatis-plus 自帶的 saveBatch 插入

@Test
voidMybatisPlusSaveBatch(){
SqlSessionsqlSession=sqlSessionFactory.openSession();
try{
ListopenTestList=newArrayList<>();
for(inti=0;i1000;i++){
OpenTestopenTest=newOpenTest();
openTest.setA("a"+i);
openTest.setB("b"+i);
openTest.setC("c"+i);
openTest.setD("d"+i);
openTest.setE("e"+i);
openTest.setF("f"+i);
openTest.setG("g"+i);
openTest.setH("h"+i);
openTest.setI("i"+i);
openTest.setJ("j"+i);
openTest.setK("k"+i);
openTestList.add(openTest);
}
StopWatchstopWatch=newStopWatch();
stopWatch.start("mybatisplussavebatch");
//批量插入
openTestService.saveBatch(openTestList);
sqlSession.commit();
stopWatch.stop();
log.info("mybatis plus save batch:"+stopWatch.getTotalTimeMillis());
}finally{
sqlSession.close();
}
}
2ae81f1e-fe8a-11ed-90ce-dac502259ad0.png

耗費的時間是 59927 毫秒,比一條一條插入快了一倍,從這點來看,效率還是可以的。

然后常見的還有一種利用拼接 sql 方式來實現(xiàn)批量插入,我們也來對比試試看性能如何。

1000條數(shù)據(jù)用手動拼接 sql 方式插入

搞個手動拼接:

2aef8592-fe8a-11ed-90ce-dac502259ad0.png來跑跑下性能如何:

@Test
voidMapperSaveBatch(){
SqlSessionsqlSession=sqlSessionFactory.openSession();
try{
ListopenTestList=newArrayList<>();
for(inti=0;i1000;i++){
OpenTestopenTest=newOpenTest();
openTest.setA("a"+i);
openTest.setB("b"+i);
openTest.setC("c"+i);
openTest.setD("d"+i);
openTest.setE("e"+i);
openTest.setF("f"+i);
openTest.setG("g"+i);
openTest.setH("h"+i);
openTest.setI("i"+i);
openTest.setJ("j"+i);
openTest.setK("k"+i);
openTestList.add(openTest);
}
StopWatchstopWatch=newStopWatch();
stopWatch.start("mappersavebatch");
//手動拼接批量插入
openTestMapper.saveBatch(openTestList);
sqlSession.commit();
stopWatch.stop();
log.info("mapper save batch:"+stopWatch.getTotalTimeMillis());
}finally{
sqlSession.close();
}
}
2af6df90-fe8a-11ed-90ce-dac502259ad0.png

耗時只有 2275 毫秒,性能比 mybatis-plus 自帶的 saveBatch 好了 26 倍!

這時,我又突然回想起以前直接用 JDBC 批量保存的接口,那都到這份上了,順帶也跑跑看!

1000條數(shù)據(jù)用 JDBC executeBatch 插入

@Test
voidJDBCSaveBatch()throwsSQLException{
SqlSessionsqlSession=sqlSessionFactory.openSession();
Connectionconnection=sqlSession.getConnection();
connection.setAutoCommit(false);

Stringsql="insertintoopen_test(a,b,c,d,e,f,g,h,i,j,k)values(?,?,?,?,?,?,?,?,?,?,?)";
PreparedStatementstatement=connection.prepareStatement(sql);
try{
for(inti=0;i1000;i++){
statement.setString(1,"a"+i);
statement.setString(2,"b"+i);
statement.setString(3,"c"+i);
statement.setString(4,"d"+i);
statement.setString(5,"e"+i);
statement.setString(6,"f"+i);
statement.setString(7,"g"+i);
statement.setString(8,"h"+i);
statement.setString(9,"i"+i);
statement.setString(10,"j"+i);
statement.setString(11,"k"+i);
statement.addBatch();
}
StopWatchstopWatch=newStopWatch();
stopWatch.start("JDBCsavebatch");
statement.executeBatch();
connection.commit();
stopWatch.stop();
log.info("JDBC save batch:"+stopWatch.getTotalTimeMillis());
}finally{
statement.close();
sqlSession.close();
}
}
2afea46e-fe8a-11ed-90ce-dac502259ad0.png

耗時是 55663 毫秒,所以 JDBC executeBatch 的性能跟 mybatis-plus 的 saveBatch 一樣(底層一樣)。

綜上所述,拼接 sql 的方式實現(xiàn)批量保存效率最佳。

但是我又不太甘心,總感覺應(yīng)該有什么別的法子,然后我就繼續(xù)跟著 mybatis-plus 的源碼 debug 了一下,跟到了 mysql 的驅(qū)動,突然發(fā)現(xiàn)有個 if 里面的條件有點顯眼:

2b05fd7c-fe8a-11ed-90ce-dac502259ad0.png

就是這個叫 rewriteBatchedStatements 的玩意,從名字來看是要重寫批操作的 Statement,前面batchHasPlainStatements 已經(jīng)是 false,取反肯定是 true,所以只要這參數(shù)是 true 就會進行一波操作。

我看了下默認是 false。

2b0cc0b2-fe8a-11ed-90ce-dac502259ad0.png

同時我也上網(wǎng)查了下 rewriteBatchedStatements 參數(shù),好家伙,好像有用!我直接將 jdbcurl 加上了這個參數(shù):

2b13cef2-fe8a-11ed-90ce-dac502259ad0.png

然后繼續(xù)跑了下 mybatis-plus 自帶的 saveBatch,果然性能大大提高,跟拼接 SQL 差不多!

2b1b2bac-fe8a-11ed-90ce-dac502259ad0.png

順帶我也跑了下 JDBC 的 executeBatch ,果然也提高了。

2b21ec58-fe8a-11ed-90ce-dac502259ad0.png

然后我繼續(xù) debug ,來探探 rewriteBatchedStatements 究竟是怎么 rewrite 的!

如果這個參數(shù)是 true,則會執(zhí)行下面的方法且直接返回:

2b30f07c-fe8a-11ed-90ce-dac502259ad0.png

看下 executeBatchedInserts 究竟干了什么:

2b37bbfa-fe8a-11ed-90ce-dac502259ad0.png

看到上面我圈出來的代碼沒,好像已經(jīng)有點感覺了,繼續(xù)往下 debug。

果然!sql 語句被 rewrite了:

2b3e7e7c-fe8a-11ed-90ce-dac502259ad0.png

對插入而言,所謂的 rewrite 其實就是將一批插入拼接成 insert into xxx values (a),(b),(c)...這樣一條語句的形式然后執(zhí)行,這樣一來跟拼接 sql 的效果是一樣的。

那為什么默認不給這個參數(shù)設(shè)置為 true 呢?

原來是這樣的:

  1. 如果批量語句中的某些語句失敗,則默認重寫會導(dǎo)致所有語句都失敗。
  2. 批量語句的某些語句參數(shù)不一樣,則默認重寫會使得查詢緩存未命中。

看起來影響不大,所以我給我的項目設(shè)置上了這個參數(shù)!

基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

最后

稍微總結(jié)下我粗略的對比(雖然粗略,但實驗結(jié)果符合原理層面的理解),如果你想更準確地實驗,可以使用JMH,并且測試更多組數(shù)(如 5000,10000等)的情況。

批量保存方式 數(shù)據(jù)量(條) 耗時(ms)
單條循環(huán)插入 1000 121011
mybatis-plus saveBatch 1000 59927
mybatis-plus saveBatch(添加rewtire參數(shù)) 1000 2589
手動拼接sql 1000 2275
jdbc executeBatch 1000 55663
jdbc executeBatch(添加rewtire參數(shù)) 1000 324

所以如果有使用 jdbc 的 Batch 性能方面的需求,要將 rewriteBatchedStatements 設(shè)置為 true,這樣能提高很多性能。

然后如果喜歡手動拼接 sql 要注意一次拼接的數(shù)量,分批處理。


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    626

    瀏覽量

    28965
  • 接口處理
    +關(guān)注

    關(guān)注

    0

    文章

    3

    瀏覽量

    6396
  • mybatis
    +關(guān)注

    關(guān)注

    0

    文章

    58

    瀏覽量

    6691

原文標題:調(diào)優(yōu) MyBatis 25 倍性能

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    MMC SW調(diào)優(yōu)算法

    電子發(fā)燒友網(wǎng)站提供《MMC SW調(diào)優(yōu)算法.pdf》資料免費下載
    發(fā)表于 09-20 11:14 ?0次下載
    MMC SW<b class='flag-5'>調(diào)</b><b class='flag-5'>優(yōu)</b>算法

    TAS58xx系列通用調(diào)優(yōu)指南

    電子發(fā)燒友網(wǎng)站提供《TAS58xx系列通用調(diào)優(yōu)指南.pdf》資料免費下載
    發(fā)表于 09-14 10:49 ?0次下載
    TAS58xx系列通用<b class='flag-5'>調(diào)</b><b class='flag-5'>優(yōu)</b>指南

    AM6xA ISP調(diào)優(yōu)指南

    電子發(fā)燒友網(wǎng)站提供《AM6xA ISP調(diào)優(yōu)指南.pdf》資料免費下載
    發(fā)表于 09-07 09:52 ?0次下載
    AM6xA ISP<b class='flag-5'>調(diào)</b><b class='flag-5'>優(yōu)</b>指南

    OSPI控制器PHY調(diào)優(yōu)算法

    電子發(fā)燒友網(wǎng)站提供《OSPI控制器PHY調(diào)優(yōu)算法.pdf》資料免費下載
    發(fā)表于 08-30 11:12 ?0次下載
    OSPI控制器PHY<b class='flag-5'>調(diào)</b><b class='flag-5'>優(yōu)</b>算法

    深度解析JVM調(diào)優(yōu)實踐應(yīng)用

    Tomcat自身的調(diào)優(yōu)是針對conf/server.xml中的幾個參數(shù)的調(diào)優(yōu)設(shè)置。首先是對這幾個參數(shù)的含義要有深刻而清楚的理解。
    的頭像 發(fā)表于 04-01 10:24 ?341次閱讀
    深度解析JVM<b class='flag-5'>調(diào)</b><b class='flag-5'>優(yōu)</b>實踐應(yīng)用

    鴻蒙開發(fā)實戰(zhàn):【性能調(diào)優(yōu)組件】

    性能調(diào)優(yōu)組件包含系統(tǒng)和應(yīng)用調(diào)優(yōu)框架,旨在為開發(fā)者提供一套性能
    的頭像 發(fā)表于 03-13 15:12 ?304次閱讀
    鴻蒙開發(fā)實戰(zhàn):【<b class='flag-5'>性能</b><b class='flag-5'>調(diào)</b><b class='flag-5'>優(yōu)</b>組件】

    優(yōu)于10參數(shù)模型!微軟發(fā)布Orca 2 LLM

    微軟發(fā)布 Orca 2 LLM,這是 Llama 2 的一個調(diào)優(yōu)版本,性能與包含 10 參數(shù)的模型相當(dāng),甚至更好。
    的頭像 發(fā)表于 12-26 14:23 ?516次閱讀

    jvm調(diào)優(yōu)工具有哪些

    JVM調(diào)優(yōu)是提高Java應(yīng)用程序性能的重要手段,而JVM調(diào)優(yōu)工具則是輔助開發(fā)人員進行調(diào)
    的頭像 發(fā)表于 12-05 11:44 ?911次閱讀

    jvm調(diào)優(yōu)常用命令

    JVM調(diào)優(yōu)是提升Java應(yīng)用性能的一個重要方面,通過合理設(shè)置JVM參數(shù)可以達到優(yōu)化應(yīng)用性能、提高系統(tǒng)穩(wěn)定性的目的。本文將為你詳細介紹JVM調(diào)
    的頭像 發(fā)表于 12-05 11:43 ?575次閱讀

    jvm調(diào)優(yōu)主要是調(diào)哪里

    JVM調(diào)優(yōu)主要涉及內(nèi)存管理、垃圾回收、線程管理與鎖優(yōu)化等方面。下面將詳細介紹每個方面的調(diào)優(yōu)技術(shù)和策略以及如何進行優(yōu)化。 內(nèi)存管理 JVM的內(nèi)存管理主要包括堆內(nèi)存、棧內(nèi)存和非堆內(nèi)存。堆內(nèi)
    的頭像 發(fā)表于 12-05 11:37 ?1321次閱讀

    jvm參數(shù)的設(shè)置和jvm調(diào)優(yōu)

    JVM(Java虛擬機)參數(shù)的設(shè)置和調(diào)優(yōu)對于提高Java應(yīng)用程序的性能和穩(wěn)定性非常重要。在本文中,我們將詳細介紹JVM參數(shù)的設(shè)置和調(diào)優(yōu)方法。
    的頭像 發(fā)表于 12-05 11:36 ?1042次閱讀

    jvm調(diào)優(yōu)參數(shù)

    JVM(Java虛擬機)是Java程序的運行環(huán)境,它負責(zé)解釋Java字節(jié)碼并執(zhí)行相應(yīng)的指令。為了提高應(yīng)用程序的性能和穩(wěn)定性,我們可以調(diào)優(yōu)JVM的參數(shù)。 JVM調(diào)
    的頭像 發(fā)表于 12-05 11:29 ?518次閱讀

    什么場景需要jvm調(diào)優(yōu)

    JVM調(diào)優(yōu)是指對Java虛擬機進行性能優(yōu)化和資源管理,以提高應(yīng)用程序的運行效率和吞吐量。JVM調(diào)優(yōu)的場景有很多,下面將詳細介紹各種不同的場景
    的頭像 發(fā)表于 12-05 11:14 ?1073次閱讀

    javajvm調(diào)優(yōu)有幾種方法

    JVM調(diào)優(yōu)是Java應(yīng)用程序性能優(yōu)化過程中的重要步驟,它通過針對JVM進行優(yōu)化來提高應(yīng)用程序的性能和可靠性。JVM調(diào)
    的頭像 發(fā)表于 12-05 11:11 ?1843次閱讀

    HarmonyOS NEXT調(diào)優(yōu)工具Smart Perf Host高效使用指南

    參考以下路徑進行編譯部署。 **截止到目前,Smart Perf Host能力規(guī)劃中已支持近30個開發(fā)能力,25個能力正處于開發(fā)測試階段,能力覆蓋性能調(diào)優(yōu)模板、工具系統(tǒng)能力、系統(tǒng)依賴
    發(fā)表于 11-09 08:35