mybatis plus saveBatch方法方法執(zhí)行慢導致接口發(fā)送慢解決分析
引言
作者今天在開發(fā)一個后臺發(fā)送消息的功能時,由于需要給多個用戶發(fā)送消息,于是使用了 mybatis plus
提供的 saveBatch()
方法,在測試環(huán)境測試通過上預發(fā)布后,測試反應發(fā)送消息接口很慢得等 5、6 秒,于是我就登錄預發(fā)布環(huán)境查看執(zhí)行日志,發(fā)現(xiàn)是 mybatis plus
提供的 saveBatch()
方法執(zhí)行很慢導致,于是也就有了本篇文章。
mybatis plus
mybatis plus 是一個流行的 ORM 框架,它基于 mybatis,提供了很多便利的功能,比如代碼生成器、通用 CRUD、分頁插件、樂觀鎖插件等。它可以讓我們更方便地操作數(shù)據(jù)庫,減少重復的代碼,提高開發(fā)效率。
注意:本文所使用的 mybatis plus 版本是 3.5.2 版本。
案發(fā)現(xiàn)場還原
/** * 先保存通知消息,在批量保存用戶通知記錄 */ @Transactional(rollbackFor = Exception.class) @Override public boolean saveNotice(Notify notify, String receiveUserIds) { long begin = System.currentTimeMillis(); notify.setCreateTime(new Date()); notify.setCreateBy(ShiroUtil.getSessionUid()); if (notify.getPublishTime() == null) { notify.setPublishTime(new Date()); } boolean insert = save(notify); List<NotifyRecord> collect = new ArrayList<>(); List<String> receiveUserList = fillNotifyRecordList(notify, receiveUserIds, collect); notifyRecordService.saveBatch(collect); long end = System.currentTimeMillis(); System.out.println(end - begin); ... return insert; } /** * 根據(jù)用戶id,組裝用戶通知記錄集合,返回200條記錄 */ public List<String> fillNotifyRecordList(Notify notify, String receiveUserIds, List<NotifyRecord> collect) { List<String> noticeRecordList = new ArrayList<>(200); ... // 組將兩百條用戶通知記錄 return noticeRecordList; }
如上代碼,我有一個 saveNotice()
方法用于保存通知消息以及用戶通知記錄。執(zhí)行邏輯如下,
- 保存通知消息
- 根據(jù)用戶 id,組裝用戶通知記錄集合,返回 200 條用戶通知記錄
- 批量保存用戶通知記錄集合
前兩步驟耗時都很少,我們直接看第三步操作耗時,結(jié)合 sql 執(zhí)行日志,如下,
-- slow sql 5542 millis. INSERT INTO oa_notify_record ( notifyId, receiveUserId, receiveUserName, isRead, createTime ) VALUES ( ?, ?, ?, ?, ? )[225,"fcd90fe3990e505d07c90a238f75e9c1","niuwawa",false,"2023-10-30 23:54:04"] 5681
再結(jié)合 mybatis free log
插件打印完整 sql 如下圖,
可以看出,我們批量保存用戶通知記錄是一條一條保存得,已經(jīng)可以猜測就是批量插入方法導致耗時較高。
這里使用 mybatis log free 插件,它可以自動幫我們在控制臺打印完整得 mybatis sql 語句。有需要可以在 idea 插件中心搜索 mybatis log free 下載安裝。
結(jié)合 saveBatch()
底層源碼也能夠看出,mybatis plus
對于批量操作是在 executeBatch() 方法內(nèi)使用 for
循環(huán)執(zhí)行插入操作得,源碼如下圖,
到這里我們應該也能猜出了在測試環(huán)境執(zhí)行較快得原因,因為在測試環(huán)境需要批量保存得用戶通知記錄比較少,只有幾條記錄,所以很快。但是上預發(fā)布后,由于預發(fā)布中需要批量保存得用戶通知記錄比較多達到了數(shù)百條,所以執(zhí)行較慢,耗時達到了 5、6 秒之久。
由上述源碼可以看出,mybatis plus
的批量操作底層使用的還是 mybatis
提供的 batch
模式實現(xiàn)批量插入以及更新的。而 mybatis
提供的 batch
模式操作底層使用的還是 jdbc
驅(qū)動提供的批量操作模式,jdbc
批量操作示例代碼如下,
public static void main(String[] args) { Connection conn = null; PreparedStatement statement = null; try { // 數(shù)據(jù)庫連接 String url = "jdbc:mysql://*************?autoReconnect=true&nullCatalogMeansCurrent=true&failOverReadOnly=false&useUnicode=true&characterEncoding=UTF-8"; String user = "******"; String password = "************"; // 添加批處理參數(shù) // url = url + "&rewriteBatchedStatements=true"; // 加載驅(qū)動類 Class.forName("com.mysql.cj.jdbc.Driver"); // 創(chuàng)建連接 conn = DriverManager.getConnection(url, user, password); // 創(chuàng)建預編譯 sql 對象 statement = conn.prepareStatement("UPDATE table_test_demo set code = ? where id = ?"); long a = System.currentTimeMillis(); // 計時 // 這里添加 100 個批處理參數(shù) for (int i = 1; i <= 100; i++) { statement.setString(1, "測試1"); statement.setInt(2, i); statement.addBatch(); // 批量添加 } long b = System.currentTimeMillis(); // 計時 System.out.println("添加參數(shù)耗時:" + (b-a)); // 計時 int[] r = statement.executeBatch(); // 批量提交 statement.clearBatch(); // 清空批量添加的 sql 命令列表緩存 long c = System.currentTimeMillis(); // 計時 System.out.println("執(zhí)行sql耗時:" + (c-b)); // 計時 } catch (Exception e) { e.printStackTrace(); } finally { // 主動釋放資源 try { if (statement != null) { statement.close(); } if (conn != null) { conn.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } } }
statement.addBatch()
將 sql 語句打包到一個容器中statement.executeBatch()
將容器中的 sql 語句提交statement.clearBatch()
清空容器,為下一次打包做準備
推薦博主開源的 H5 商城項目waynboot-mall,這是一套全部開源的微商城項目,包含三個項目:運營后臺、H5 商城前臺和服務端接口。實現(xiàn)了商城所需的首頁展示、商品分類、商品詳情、商品 sku、分詞搜索、購物車、結(jié)算下單、支付寶/微信支付、收單評論以及完善的后臺管理等一系列功能。 技術(shù)上基于最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中間件。分模塊設(shè)計、簡潔易維護
github 地址:https://github.com/wayn111/waynboot-mall
那么問題出現(xiàn)在哪里了?明明已經(jīng)使用了批量操作,但耗時還是很慢,別急,跟著我往下看。
解決方法
到這里,也就是本文得重點所在了,那怎么解決這個問題嘞?如何既利用 mybatis plus
提供得便攜性,也能夠解決批量操作耗時較高得問題。
雖然我們使用了 mybatis plus -> mybatis -> jdbc
這一條批量操作鏈路,但是其實我們還需要在 jdbcurl
上添加一個 rewriteBatchedStatements=true
參數(shù)即可解決這個問題。
- MySQL 的 JDBC 連接的 url 中要加 rewriteBatchedStatements 參數(shù),并保證 5.1.13 以上版本的驅(qū)動,才能實現(xiàn)高性能的批量插入。
- MySQL JDBC 驅(qū)動在默認情況下會無視 executeBatch()語句,把我們期望批量執(zhí)行的一組 sql 語句拆散,一條一條地發(fā)給 MySQL 數(shù)據(jù)庫,批量插入實際上是單條插入,直接造成較低的性能。只有把 rewriteBatchedStatements 參數(shù)置為 true, 驅(qū)動才會幫你批量執(zhí)行 SQL。另外這個選項對 INSERT/UPDATE/DELETE 都有效。
- rewriteBatchedStatements=true 的意思是,當你在 Java 程序中使用批量插入/修改/刪除(batching)時,MySQL JDBC 驅(qū)動程序?qū)L試重新編寫(rewrite)你的 SQL 語句,以便更有效地執(zhí)行這些批量插入操作。
OK,在我們給 jdbcurl
上添加了參數(shù)后,看看效果,如下圖,
可以看到 jdbcurl
添加了 rewriteBatchedStatements=true
參數(shù)后,批量操作的執(zhí)行耗時已經(jīng)只有 200 毫秒,自此也就解決了 mybatis plus
提供的 saveBatch()
方法執(zhí)行耗時較高得問題。
總結(jié)
mybatis plus
給開發(fā)人員帶來了很多便利,但是其中也有一些坑點,比如上文所提到得批量操作耗時問題,如果不注意的話,就有可能調(diào)入坑里,各位開發(fā)同學可以檢查自己或者公司項目中 jdbcurl
是否缺失 rewriteBatchedStatements=true
參數(shù),加以改正,避免重復掉入這個坑里。
以上就是mybatis plus saveBatch方法導致接口發(fā)送慢解決分析的詳細內(nèi)容,更多關(guān)于mybatis plus saveBatch接口發(fā)送的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于JAVA中使用Axis發(fā)布/調(diào)用Webservice的方法詳解
如果初識axis發(fā)布/調(diào)用WS,建議先讀上面的參考文件,本文對于發(fā)布/調(diào)用WS的主要步驟只是簡單文字描述,沒有它寫的詳盡2013-05-05Springboot公共字段填充及ThreadLocal模塊改進方案
這篇文章主要為大家介紹了Springboot公共字段填充及ThreadLocal模塊改進方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11SpringCloud使用Feign實現(xiàn)動態(tài)路由操作
這篇文章主要介紹了SpringCloud使用Feign實現(xiàn)動態(tài)路由操作,文章圍繞主題展開詳細的內(nèi)容介紹具有一定的參考價值,需要的小伙伴可以參考一下2022-06-06javaweb Servlet開發(fā)總結(jié)(一)
Servlet是sun公司提供的一門用于開發(fā)動態(tài)web資源的技術(shù)。這篇文章主要介紹了javaweb Servlet開發(fā)的第一篇,感興趣的小伙伴們可以參考一下2016-05-05Mybatis-Plus3.2.0 MetaObjectHandler 無法進行公共字段全局填充
這篇文章主要介紹了Mybatis-Plus3.2.0 MetaObjectHandler 無法進行公共字段全局填充,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11