那些年我們爬過的山 - mybatis批量導入

寫這篇文章之前想着給這篇博客起一個文藝一點的標題,思來想去,想到了那些年我們爬過的山,或者我們一起趟過的河?代碼不規範,同事兩行淚,這是多麼痛的領悟啊!

背景

本組一名實習生,由於學校有事情需要回去處理,我便將他的代碼接管過來,正好趕上本次迭代上線,需要將同事的代碼提交測試,如果被測試出來有bug,我就來負責bug修復,代碼優化等。由於不同的開發人員都有自己的編程習慣,所以不同人員所寫的代碼多少都會有些差異,比如:變量的命名、代碼的格式等,即使組內有一套開發規範也還是會出現一些差異,這就是所謂的個性?特點? 當然 特點只是特點,並不能稱得上個性。既然風格不同,那麼看別人代碼的時候會有值得學習的地方,也有自己感覺不舒服的地方。也罷,畢竟人都有“個性”。

問題現象

這次我接手同事的代碼主要是一個Excel導入數據庫這樣一個功能,在測試人員測試導入數據的時候,使用的導入數據有2w條作爲測試數據導入數據庫,現象是 導入後發現頁面先是顯示上傳中,之後頁面沒有任何反饋,經排查後發現是因爲後臺還在處理導入數據邏輯,時間過長頁面沒有得到反饋,導致頁面超時。

排查過程

  • 接着查看了導入邏輯,發現導入的時候,使用的是將2w條數據循環導入數據庫,每一次導入都會進行數據庫交互,導致數據庫連接池的連接被耗盡,會再次進行創建、分配、釋放等操作,從而導致之後的處理變得緩慢。
  • 基於這些考慮使用了批量導入,批量導入的目的就是一次導入多條數據,減少和數據庫交互次數,減輕數據庫壓力。
  • 修改程序後測試導入所用的時間並不理想,繼續排查發現 在導入前還有判斷卡密是否存在數據庫 這樣一個操作,和循環插入沒有什麼區別,也是會循環2w次數據庫查詢。因此首先想到的是減少和數據庫的交互次數,先把數據查詢出來,放到內存中,再將兩個集合進行比較,發現數據量小的時候還可以,數據量大了就會很緩慢,嘗試了幾個網上搜到的“高效”list去重,發現效果並不明顯。
  • 遂繼續找其他的方法,最後找到使用MySQL的ignore關鍵字【作用:若有導致unique key 衝突的記錄,則該條記錄不會被插入到數據庫中,去重字段一定要是唯一索引】就可以解決判斷卡密重複的問題,** 經過測試 導入2w條數據由原來的16分鐘,減少到目前的10秒 左右** 。

總結

對於本次問題的排查,可以總結爲問題發現,和問題排查。發現問題時首先要快速瞭解該功能的主要邏輯是什麼,這次的導入主要有兩點,一是 導入前判斷是否有和數據庫重複,二是導入操作。弄明白主要邏輯之後,就要分析,導入慢肯定是這兩個邏輯的某一個邏輯慢,或者是兩個邏輯都慢。經過分析發現,這兩個邏輯都出現了循環建立數據庫連接的問題,發現了問題的根本原因後,就要減少建立數據庫連接次數,問題便得到解決。

主要代碼

對於導入前和數據庫判斷是否有重複的,使用了mysql的一個關鍵字ignore,關鍵字的作用是:若有導致unique key 衝突的記錄,則該條記錄不會被插入到數據庫中,去重字段一定要是唯一索引。其他就是拼接SQL使用批量導入。下面將主要的代碼貼出來供大家參考。

  • 批量導入時每次批量插入100條數據,這樣數據庫連接建立的次數就從原來的 20000次減少到200次。
        // 批量入庫,每次批量插入100條
List<Detail> detailList = new ArrayList<>(); 
List<List<Detail>> tempList = new ArrayList<>();
    int insertCount = 100;
    for (int i = 0; i < detailList.size(); i += insertCount) {
        if ((i + insertCount) < detailList.size()) {
            List<Detail> newList = new ArrayList<>();
            newList.addAll(detailList.subList(i, i + insertCount));
            tempList.add(newList);
        } else {
            List<Detail> newList = new ArrayList<>();
            newList.addAll(detailList.subList(i, detailList.size()));
            tempList.add(newList);
        }
    }
Map<String, Object> map = detailManager.batchInsertDetail(tempList);
  • mybatis的寫法
<insert id="batchInsertDetail" parameterType="java.util.List">
      insert ignore into my_table
        (id,code,status)
      values
        <foreach collection ="list" item="detail" index= "index" separator =",">
            (#{detail.id,jdbcType=BIGINT},#{detail.code,jdbcType=VARCHAR},#{detail.status,jdbcType=TINYINT})
        </foreach >
</insert>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章