距离上次的博客已经有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条记录是有很大差距的。

一次EF批量插入多表数据的性能优化经历的更多相关文章

  1. mybatis批量插入oracle大量数据记录性能问题解决

    环境: mybatis  + oracle11g r2 1.使用"直接路径插入"(以下sql语句中的"/*+append_values */"),而且使用key ...

  2. EF批量插入数据耗时对比

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  3. EF批量插入太慢?那是你的姿势不对

    大概所有的程序员应该都接触过批量插入的场景,我也相信任何的程序员都能写出可正常运行的批量插入的代码.但怎样实现一个高效.快速插入的批量插入功能呢? 由于每个人的工作履历,工作年限的不同,在实现这样的一 ...

  4. mysql命令行批量插入100条数据命令

    先介绍一个关键字的使用: delimiter 定好结束符为"$$",(定义的时候需要加上一个空格) 然后最后又定义为";", MYSQL的默认结束符为" ...

  5. oracle 使用occi方式 批量插入多条数据

    if (vecInfo.empty()) { ; //数据为空,不上传,不上传标志设置为1,只有0表示上传成功 } std::string strUserName = userName; std::s ...

  6. DedeCMS数据负载性能优化方案简单几招让你提速N倍

    前文介绍了DedeCMS栏目列表页实现完美分页的方法,避免了大部分重复栏目标题对搜索引擎的影响,对SEO更有利.今天,分享一下DedeCMS数据负载性能优化的方法. 接触织梦也有三年多时间了,对它可谓 ...

  7. mongodb可以通过profile来监控数据 (mongodb性能优化)

    mongodb可以通过profile来监控数据 (mongodb性能优化)   开启 Profiling  功能 ,对慢查询进行优化: mongodb可以通过profile来监控数据,进行优化. 查看 ...

  8. EF批量插入数据(Z.EntityFramework.Extensions)

    EF用原生的插入数据方法DbSet.ADD()和 DbSet.AddRange()都很慢.所以要做大型的批量插入只能另选它法. 1.Nugget 2.代码 using EF6._0Test.EF; u ...

  9. 将大量数据批量插入Oracle表的类,支持停止续传

    之前用create table select * from XXTable无疑是创建庞大表的最快方案之一,但是数据重复率是个问题,且数据难以操控. 于是我在之前批量插数据的基础上更新了一个类,让它具有 ...

随机推荐

  1. Ubuntun CentOS的ISO官方MD5在哪里查看(安装虚拟电脑时出现严重错误的解决方法)

    近日在VirtualBox虚拟机上安装Linux,然后果断的选择了Ubuntu.当我新建虚拟机,一切配置完成之后,启动虚拟机,还没开始安装就提示虚拟电脑出现严重错误,需要关闭.起初以为是配置错了,上网 ...

  2. FilterDispatcher已被标注为过时解决办法 >>> FilterDispatcher <<< is deprecated!

    一些struts2的教程都是比较早的,当我们基于较新版本的struts2来实现代码的时候,往往会出现一些问题.比如这个警告:FilterDispatcher isdeprecated! 在web.xm ...

  3. centos yum Segmentation fault 问题解决办法

    今儿在centos 使用yum 安装软件时出现了 ”Segmentation fault“ 错误提示,google一大把执行 yum clean all 命令后,再执行还是没用,最后把 zlib.x. ...

  4. JS高程3.基本概念(1)

    1.语法 (1)ECMAScript中的一切(变量,函数名和操作符)都是区分大小写的. (2)标识符 标识符的第一个字符必须是字母,下划线或是美元符号. 其他字符可以是字母,下划线,美元符号和数字. ...

  5. JavaScript 变量声明提前

    <JavaScript权威指南>中指出:JavaScript变量在声明之前已经可用,JavaScript的这个特性被非正式的称为声明提前(hoisting),即JavaScript函数中声 ...

  6. 网络分析之networkx(转载)

    图的类型 Graph类是无向图的基类,无向图能有自己的属性或参数,不包含重边,允许有回路,节点可以是任何hash的python对象,节点和边可以保存key/value属性对.该类的构造函数为Graph ...

  7. DrawerLayout的openDrawer()和closeDrawer()方法

    如下代码 DrawerLayout mdrawerLayout; Button btn; ------------------------------------------------------- ...

  8. 利用split

    java.lang.string.splitsplit 方法将一个字符串分割为子字符串,然后将结果作为字符串数组返回.stringObj.split([separator,[limit]])strin ...

  9. 基于Ruby的watir-webdriver自动化测试方案与实施(一)

    基于Ruby的watir-webdriver自动化测试方案与实施(五)   基于Ruby的watir-webdriver自动化测试方案与实施(四)   基于Ruby的watir-webdriver自动 ...

  10. margin css的外边距

    h2{margin:10px 0;} div{margin:20px 0;} ...... <h2>这是一个标题</h2> <div> <h2>这是又一 ...