MyBatis批量插入和性能问题

在有批量插入数据库的需求时,比如插入50条数据,传统的做法是利用for循环50次,每条数据挨个插入。此种方式存在严重效率问题,需要频繁获取Session,获取连接。

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

[html]  view plain  copy
  1. <insert id="insertByBatch" parameterType="java.util.List">  
  2.         BEGIN  
  3.         <foreach collection="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代码:

[java]  view plain  copy
  1. public void 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条,则分三次执行全部插入。所耗费的时间要比一次执行的时间要短。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值