zoukankan      html  css  js  c++  java
  • 一次EF批量插入多表数据的性能优化经历

      距离上次的博客已经有15个多月了,感慨有些事情还是需要坚持,一旦停下来很有可能就会停很久或者从此再也不会坚持。但我个人一直还坚持认为属于技术狂热份子,且喜欢精益求精的那种。最近遇到两个和数据迁移相关的项目,均遇到需要性能优化的问题,这里拿第二个项目的一个小优化过程与大家分享,技术并不高深,我注重的是解决问题的过程。我的方案是有业务背景以及技术背景限制的,不一定适合其它项目,优化是相对的。


      业务场景:我们需要迁移一批老的合同订单数据,其有一个合同的订单数为519条,迁移到新表中会涉及到主要的4个表,就是说519条老数据,会变成519*4。
     
      技术背景:数据库是mysql,后台采用的是微软的EF


     


      问题:迁移这批订单当时最好的性能方案是14秒(未优化前是分钟级别),我们总共有400000订单,算下最理想状态下的总时间:=(14/519)*400000/3600=3小时,再算下取数据,转换数据的时间,基本要4小时。如果中途有异常,这个时间可能需要一夜甚至更长的时间才能迁移完,这真正恶梦。
     
      先来看看优化前:优化前导519个合同是分钟级别的,看下代码后我的方案是分三步:


      1:按批次,比如10个合同一批来操作,将后续需要的数据全部取出来。原方案是用到哪查到哪,试想400000的订单还不查个一天两天的。
      2:转换数据,将源数据转换成新的对象集合,此处不操作数据库。
      3:批量插入数据,将转换后校验无误的数据导入数据库,原方案是逻辑到哪,哪就插入数据库。不便于定位性能瓶颈也不方便进行有针对性的优化。
     
      根据以上三个步骤,我们就很容易精确定位是哪方面慢了,是查询数据库慢,转换数据慢,还是插入数据库慢。
     
      经过此方面调整后的结果:
      1:一次性读取数据后,性能明显提升,降低了数据库读取次数,享受到了批量取数据的好处;
      2:定位到性能瓶颈在于数据库插入,总时间15秒,保存数据花了14秒


     


      疑惑:我对保存519*4条数据需要14秒的结果不满意,我坚定认为数据库插入如此小数量级的数量不需要这么长的时间。再次分析,发现我们需要保存多张表的数据,且相互之间存在依赖关系,即第二张表的数据需要第一张表插入后的主键,这样我们在写EF时,会出现多条SaveChange的方法。519个订单做循环,SaveChange的总次数:519*3。
     
      改造:分三次数据库操作
      1:先全量保存第一个被依赖的表,此时由于EF的数据追踪功能,插入数据库后,对象上会自动赋值主键信息;
      2:再全量保存第二个被依赖的表,由于被依赖的表在第一步已经更新成功,此处能够成功获取到外键;
      3:最后全量保存第三被依赖的表。


     


      此方案的SaveChange次数降低到3次,执行时间变更5.5秒,性能提高接近200%。
     
     
      数据库事务,如果我们的操作加上事务会怎样?我们从优化前的版本开始看(不是上面提到的分打开三次数据库批量操作):主要利用TransactionScope来完成

    using (var trans = new TransactionScope(TransactionScopeOption.Required,
                       new TransactionOptions()
                       {
                           Timeout = new TimeSpan(0, 0, 240),
                           IsolationLevel =
                               System.Transactions.IsolationLevel.RepeatableRead
                       }))
    


      1:519*3次SaveChange,最外层嵌套一个大事务,不嵌套是58秒,嵌套了50秒,两者相关不大,如果是一个DbContext出现大量的SaveChange,有事务从结果来看性能更优化,具体原因不明,待调查。
      2:519*3次SaveChange,缩小事务范围,将事务放在循环体内部,结果变成14秒,看来小事务还是值得推荐的。
     
      再看改造后的分三次数据库操作每次一次SaveChange的场景:外面嵌套一个大事务,嵌套是5.5秒,不嵌套是5.8,相差不大。


     


      单一职责,上面的批量插入数据库使用了三次打开数据库,每次只有一个SaveChange,那么在一个DbContext中操作调用三次SaveChange呢?
      在一个DbContext中做三次SaveChange是32秒,采用三个DbContext分开操作是5.5秒,结论是大批量数据插入,避免在同一DbContext中做多次SaveChange。

     

      结论:

      1:避免使用大的数据库事务,尽量控制在有需求时打开,不需要时及时关闭,它会锁定资源的;

      2:批量插入表数据,尽量避免在同一DbContext下做多次SaveChange操作;

      3:如果有大批数据需要插入表,尽量采用单表集中插入后再操作后续表,避免插入一条数据SaveChange一次;

      4:读取数据尽量按批量读取,避免取一条数据读取一次:查询100次单条记录与一次性查询100条记录是有很大差距的。
     

  • 相关阅读:
    python之字典方法
    python之字符串方法
    python strip()方法使用
    Airtest自动化测试工具介绍
    selenium 环境配置
    一个自定义线程池的小Demo
    简单工厂模式
    BootStrap入门_创建第一个例子
    MongoDB的索引
    MongoDB的查询
  • 原文地址:https://www.cnblogs.com/ASPNET2008/p/4604866.html
Copyright © 2011-2022 走看看