距离上次的博客已经有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. [原创小工具]软件内存、CPU使用率监视,应用程序性能监测器 v3.0 绿色版

    应用程序性能监测器 V3.0 更新内容:    1.对一些代码进行了修改,软件本身的性能有所提升. 应用程序性能监测器 V2.0 更新内容:     1.鼠标移动到曲线区域,显示相关的曲线值      ...

  2. GJM : C#语言学习笔记

    --------------------------------------C#--------------------------------------if (tom == null) tom = ...

  3. jQuery鼠标经过显示大图

    效果:http://keleyi.com/keleyi/phtml/image/8.htm 以下是完整代码: <!DOCTYPE html> <html lang="en& ...

  4. (转)TortoiseSVN与VisualSVN Server搭建SVN版本控制系统

    本片主要介绍如何搭建SVN版本控制系统,主要使用工具: 1 客户端:TortoiseSVN (小乌龟) 2 服务端:VisualSVN Server 搭建出图形化管理,以及右键菜单版本控制管理的SVN ...

  5. iOS多线程简介

    1.进程 什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开迅雷.Xcode,系统就会分别启动2个进程 2.线程 什么是 ...

  6. yum使用点滴

    yum下载依赖rpm包 先安装一个yum-downloadonly 1 yum install yum-downloadonly完成安装后,yum –help在最后就提示两个命令参数,分别是: Plu ...

  7. 设置statusBarStyle

    设置状态栏的样式, typedef NS_ENUM(NSInteger, UIStatusBarStyle) {    UIStatusBarStyleDefault                  ...

  8. 安卓开发之activity详解(sumzom)

    app中,一个activity通常是指的一个单独的屏幕,相当于网站里面的一个网页,它是对用户可见的,它上面可以显示一些控件,并且可以监听处理用户的时间做出响应. 那么activity之间如何进行通信呢 ...

  9. pull解析器: 反序列化与序列化

    pull解析器:反序列化 读取xml文件来获取一个对象的数据 import java.io.FileInputStream; import java.io.IOException; import ja ...

  10. iOS 本地推送通知

    1.什么是本地推送通知 不需要联网的情况下,应用程序经由系统发出的通知 2.本地推送的使用场景 定时提醒,如玩游戏.记账.闹钟.备忘录等 3.实现本地推送通知的步骤 创建本地推送通知的对象UILoca ...