解決spring data jpa saveAll() 保存過慢問題

spring data jpa saveAll() 保存過慢

問題發現

今天在生產環境執行保存數據時 影響隊列中其他程序的運行 隨後加日志排查 發現 執行 4500條 insert操作時 耗時 9分鐘 我類個去…

解決方案1 此方案在第二天失效瞭

廢話不多說 直接上配置文件參數

application-prod.yml 部分參數如下

  jpa:
  show-sql: false
  hibernate:
    ddl-auto: none
  properties:
    hibernate:
      jdbc:
        #為spring data jpa saveAll方法提供批量插入操作 此處可以隨時更改大小 建議500哦
        batch_size: 500
        batch_versioned_data: true
        order_inserts: true

通過日志打印 執行結果如下

未開批處理 4507條 耗時: 227167ms

開啟批處理 500/次 4507條 耗時: 29140ms

開啟批處理 1000/次 4507條 耗時: 29631ms

以上方案有問題,下面附上徹底解決的截圖和記錄

後來發現在生產運行瞭一天 還是會導致保存阻塞的問題 100條保存耗時 9分鐘!!!

數據庫此時數據有300w條

於是分析 saveAll()的源代碼

在這裡插入圖片描述

在這裡插入圖片描述

原來這個保存的時候 會去數據庫查詢這條數據是否存在 如果存在 則修改 不存在則直接添加 如下圖

在這裡插入圖片描述

重寫 saveAll() 的方法 就是仿照它 for循環裡面直接調用 save()方法

 @PersistenceContext()
 protected EntityManager em;

在這裡插入圖片描述

此處你們可以改成泛型的方式,提取公共類,封裝一下即可。

JPA的saveAll方法執行效率很差

springboot項目中使用瞭SpringDataJpa的技術,很方便,省瞭很多dao層繁瑣的步驟,但是有一個接口需要批量更新或者插入,數據量挺大,大概1-2w條,每條記錄20-30個字段吧,對於剛工作不久的我還是比較大的。我開始使用的saveAll(),因為本地單元測試,也沒考慮那麼多(其實更早期更蠢,遍歷再save,壓根不去考慮數據庫連接池的壓力或者說每次遍歷都要去連接數據庫的時間損耗…),但是客戶壓力測試,我的接口就拉胯瞭。接口等待時間太久…誒

剛開始的思路想解決saveAll方法為什麼這麼慢的問題,因為saveAll是有則更新,無則新增,所以每條記錄都要去比對該記錄是否存在表中,效率比較差(我以後還是用JPA去適應數據量較小的吧,jpa可能還有什麼別的路子後面可以配置進去也行)。

最後改用瞭mybatis的foreach方式,雖然比較老套而且肯定不是最快的辦法,但是還是實用。

使用過程中,出現瞭這個報錯:

Packet for query is too large (3227 > 1024). You can change this value on the server by setting the max_allowed_packet’ variable.

應該是一下子丟進來的數據太大,超過瞭mysql的限制,有人說通過修改數據庫的max_allowed_packet這個屬性來修改,不過我這裡是jenkins部署的服務器,能操作到數據庫的隻有navicat這個可視化工具,如果使用命令行修改這個屬性,好像隻能暫時起效。我隻好類似分頁,把18000條數據按照2000一次批量操作,分成9次,這樣既不會報錯,也會比savelAll快一些。後續再研究一下更快更高效的方法。

數據庫配置加上allowMultiQueries=true才會支持foreach循環操作。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: