mybatis plus很好,但是我被它坑了!

waynaqua發表於2023-10-31

來源:waynblog

作者今天在開發一個後臺傳送訊息的功能時,由於需要給多個使用者傳送訊息,於是使用了 mybatis plus 提供的 saveBatch() 方法,在測試環境測試透過上預釋出後,測試反應傳送訊息介面很慢得等 5、6 秒,於是我就登入線上環境檢視執行日誌,發現是 mybatis plus 提供的 saveBatch() 方法執行很慢導致,於是也就有了本篇文章。

mybatis plus 是一個流行的 ORM 框架,它基於 mybatis,提供了很多便利的功能,比如程式碼生成器、通用 CRUD、分頁外掛、樂觀鎖外掛等。它可以讓我們更方便地運算元據庫,減少重複的程式碼,提高開發效率。

案發現場還原

/**
 * 先儲存通知訊息,在批次儲存使用者通知記錄
 */

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveNotice(Notify notifyString 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;
}

/**
 * 根據使用者id,組裝使用者通知記錄集合,返回200條記錄
 */

public List<String> fillNotifyRecordList(Notify notify, String receiveUserIds, List<NotifyRecord> collect) {
    List<String> noticeRecordList = new ArrayList<>(200);
    ...
    // 組將兩百條使用者通知記錄
    return noticeRecordList;
}

如上程式碼,我有一個 saveNotice() 方法用於儲存通知訊息以及使用者通知記錄。執行邏輯如下,

  1. 儲存通知訊息
  2. 根據使用者 id,組裝使用者通知記錄集合,返回 200 條使用者通知記錄
  3. 批次儲存使用者通知記錄集合

前兩步驟耗時都很少,我們直接看第三步操作耗時,結合 sql 執行日誌,如下,

-- 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

再結合 mybatis free log 外掛列印完整 sql 如下圖,

mybatis plus很好,但是我被它坑了!

可以看出,我們批次儲存使用者通知記錄是一條記錄一條進行儲存得,已經可以猜測就是批次插入方法導致得耗時較高。

這裡使用得是 mybatis log free 外掛,它可以自動幫我們在控制檯列印完整得 mybatis sql 語句。有需要可以在 idea 外掛中心搜尋 mybatis log free 下載安裝。

結合 saveBatch() 底層原始碼也能夠看出,mybatis plus 對於批次操作是透過 for 迴圈執行儲存操作得,原始碼如下圖,

mybatis plus很好,但是我被它坑了!

到這裡我們也就知道了在測試環境執行較快得原因,因為在測試環境需要批次儲存得使用者通知記錄比較少,只有幾條記錄,所以很快。但是上預釋出後,由於預釋出中需要批次儲存得使用者通知記錄比較多達到了數百條,所以執行較慢,耗時達到了 5、6 秒之久。

解決方法

到這裡,也就是本文得重點所在了,那怎麼解決這個問題嘞?如何既利用 mybatis plus 提供得便攜性,也能夠解決批次操作耗時較高得問題。

其實解決方法很簡單,只需要在 jdbcurl 上新增 rewriteBatchedStatements=true 引數即可解決這個問題。

MySQL 的 JDBC 連線的 url 中要加 rewriteBatchedStatements 引數,並保證 5.1.13 以上版本的驅動,才能實現高效能的批次插入。

MySQL JDBC 驅動在預設情況下會無視 executeBatch()語句,把我們期望批次執行的一組 sql 語句拆散,一條一條地發給 MySQL 資料庫,批次插入實際上是單條插入,直接造成較低的效能。只有把 rewriteBatchedStatements 引數置為 true, 驅動才會幫你批次執行 SQL。另外這個選項對 INSERT/UPDATE/DELETE 都有效。

rewriteBatchedStatements=true 的意思是,當你在 Java 程式中使用批次插入/修改/刪除(batching)時,MySQL JDBC 驅動程式將嘗試重新編寫(rewrite)你的 SQL 語句,以便更有效地執行這些批次插入操作。

OK,在我們給 jdbcurl 上新增了引數後,看看效果,如下圖,

mybatis plus很好,但是我被它坑了!

可以看到 jdbcurl 新增了 rewriteBatchedStatements=true 引數後,批次操作的執行耗時已經只有 200 毫秒,自此也就解決了 mybatis plus 提供的 saveBatch() 方法執行耗時較高得問題。

總結

mybatis plus 給開發人員帶來了很多便利,但是其中也有一些坑點,比如上文所提到得批次操作耗時問題,如果不注意的話,就有可能調入坑裡,各位開發同學可以檢查自己或者公司專案中 jdbcurl 是否缺失 rewriteBatchedStatements=true 引數,加以改正,避免重複掉入這個坑裡。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2991983/,如需轉載,請註明出處,否則將追究法律責任。

相關文章