1. 程式人生 > >MyBatis批量插入和效能問題

MyBatis批量插入和效能問題

在有批量插入資料庫的需求時,比如插入50條資料,傳統的做法是利用for迴圈50次,每條資料挨個插入。此種方式存在嚴重效率問題,需要頻繁獲取Session,獲取連線。

MyBatis支援了批量插入的配置和語法。如下:

  1. <insertid="insertByBatch"parameterType="java.util.List">
  2.         BEGIN  
  3.         <foreachcollection="list"item="item"separator=";">
  4.             INSERT INTO T_OPT_HANDOVERBILL_DETAIL (ID, HANDOVER_NO, WAYBILL_NO, TRANSPORT_TYPE, HANDOVER_GOODS_QTY,   
  5.             HANDOVER_WEIGHT, ACTUAL_WEIGHT,HANDOVER_VOLUME, ACTUAL_VOLUME, NOTES, GOODS_NAME, PACKING, BE_VALUABLE, RECEIVE_ORG_NAME,   
  6.             REACH_ORG_NAME,RECEIVER_NAME, DEST_REGION_NAME, GOODS_QTY, WAYBILL_FEE, DECLARATION_VALUE, CURRENCY_CODE, CREATE_TIME,  
  7.             ORIG_ORG_CODE, HANDOVER_TYPE, COD_AMOUNT, BE_JOIN_CAR, BE_FAST_GOODS, IS_INIT, WAYBILL_NOTES,TRANSPORT_TYPE_CODE,   
  8.             MODIFY_TIME)  
  9.          VALUES   
  10.             (#{item.id,jdbcType=VARCHAR}, #{item.handoverNO,jdbcType=VARCHAR}, #{item.waybillNO,jdbcType=VARCHAR},  
  11.             #{item.transportType,jdbcType=VARCHAR}, #{item.handoverGoodsQty,jdbcType=NUMERIC}, #{item.handoverWeight,jdbcType=NUMERIC},  
  12.             #{item.actualWeight,jdbcType
    =NUMERIC}, #{item.handoverVolume,jdbcType=NUMERIC}, #{item.actualVolume,jdbcType=NUMERIC},  
  13.             #{item.notes,jdbcType=VARCHAR}, #{item.goodsName,jdbcType=VARCHAR}, #{item.packing,jdbcType=VARCHAR},  
  14.             #{item.beValuable,jdbcType=VARCHAR}, #{item.receiveOrgName,jdbcType=VARCHAR}, #{item.reachOrgName,jdbcType=VARCHAR},  
  15.             #{item.receiverName,jdbcType=VARCHAR}, #{item.destRegionName,jdbcType=VARCHAR}, #{item.goodsQty,jdbcType=VARCHAR},  
  16.             #{item.waybillFee,jdbcType=NUMERIC}, #{item.declarationValue,jdbcType=NUMERIC}, #{item.currencyCode,jdbcType=VARCHAR},  
  17.             #{item.createTime,jdbcType=DATE}, #{item.origOrgCode,jdbcType=VARCHAR}, #{item.handoverType,jdbcType=VARCHAR},  
  18.             #{item.codAmount,jdbcType=NUMERIC}, #{item.beJoinCar,jdbcType=VARCHAR}, #{item.beFastGoods,jdbcType=VARCHAR},  
  19.             #{item.isInit,jdbcType=VARCHAR}, #{item.waybillNotes,jdbcType=VARCHAR}, #{item.transportTypeCode,jdbcType=VARCHAR},   
  20.             #{item.modifyTime,jdbcType=DATE})  
  21.         </foreach>
  22.         ;END;  
  23. </insert>
Java程式碼:
  1. publicvoid insertByBatch(List<HandOverBillDetailEntity> list) {  
  2.     long start = System.currentTimeMillis();  
  3.     getSqlSession().insert(NAME_SPACE + ".insertByBatch", list);  
  4.     long end = System.currentTimeMillis();  
  5.     System.out.println("批量插入耗時:" + (end - start) + "ms");  
  6. }  
Java程式碼中getSqlSession是通過Spring獲得。直接呼叫Spring的insert方法,將要插入表的物件list作為引數傳遞。NAME_SPACE值為mybatis配置檔案中namespace的值。利用MyBatis的foreach標籤,傳遞List物件。執行即可達到插入的效果。
此處在foreach之前和之後,用了begin和end,測試用的是Oracle,以這種方式批量插入速度回更快。

效能問題:

(1)Mybatis變數繫結不當引發的效能隱患

   在value值中,我們用到了mybatis的變數繫結。用的是#{}方式,還有一種方式${}。
預設情況下,使用#{}語法,MyBatis會用PreparedStatement語句當做佔位符,並且安全的設定PreparedStatement引數,可以避免SQL注入漏洞和釣魚漏洞,這個過程中MyBatis會進行必要的安全檢查和轉義。


示例1:用#{}
執行SQL:Select * from emp where name = #{employeeName}
引數:employeeName=>Smith
解析後執行的SQL:Select * from emp where name = ?


示例2:用${}
執行SQL:Select * from emp where name = ${employeeName}
引數:employeeName傳入值為:Smith
解析後執行的SQL:Select * from emp where name =Smith


可以看出,${}方式影響SQL語句的預編譯,Oracle編譯器解析時會進行硬解析,會造成很多臨時遊標變數的繫結,導致資料庫效能下降。所以從安全性和效能的角度出發,能使用#{}的情況下就不要使用${}

(2)一次批量插入過多造成的效能隱患

   雖然mybatis和oracle能支援批量插入,但也並不意味著不限插入語句的數量。經過實際的測試和效能驗證,一次批量插入的資料條數很大時,oracle將不支援語句執行,可能直接報錯:程式太大。而且一次插入資料量大,執行完成的耗費時間將以指數級增長。
如下為我在自身機器上做的簡單測試:


結論:a、在批量插入資料量過大時,sql總長度會超出允許的字元導致執行失敗。b、線性增加批量插入的資料條數,執行時間並非呈線性增長,而是3倍的比例增長。在此做一個大膽的推理,數量如果很大,時間將以指數級增長。針對此種問題,最簡單的解決方案是,將一批資料分多批插入,例如一批資料有220條,每次批量插入100條,則分三次執行全部插入。所耗費的時間要比一次執行的時間要短。