一次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条记录是有很大差距的。
一次EF批量插入多表数据的性能优化经历的更多相关文章
- mybatis批量插入oracle大量数据记录性能问题解决
环境: mybatis + oracle11g r2 1.使用"直接路径插入"(以下sql语句中的"/*+append_values */"),而且使用key ...
- EF批量插入数据耗时对比
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- EF批量插入太慢?那是你的姿势不对
大概所有的程序员应该都接触过批量插入的场景,我也相信任何的程序员都能写出可正常运行的批量插入的代码.但怎样实现一个高效.快速插入的批量插入功能呢? 由于每个人的工作履历,工作年限的不同,在实现这样的一 ...
- mysql命令行批量插入100条数据命令
先介绍一个关键字的使用: delimiter 定好结束符为"$$",(定义的时候需要加上一个空格) 然后最后又定义为";", MYSQL的默认结束符为" ...
- oracle 使用occi方式 批量插入多条数据
if (vecInfo.empty()) { ; //数据为空,不上传,不上传标志设置为1,只有0表示上传成功 } std::string strUserName = userName; std::s ...
- DedeCMS数据负载性能优化方案简单几招让你提速N倍
前文介绍了DedeCMS栏目列表页实现完美分页的方法,避免了大部分重复栏目标题对搜索引擎的影响,对SEO更有利.今天,分享一下DedeCMS数据负载性能优化的方法. 接触织梦也有三年多时间了,对它可谓 ...
- mongodb可以通过profile来监控数据 (mongodb性能优化)
mongodb可以通过profile来监控数据 (mongodb性能优化) 开启 Profiling 功能 ,对慢查询进行优化: mongodb可以通过profile来监控数据,进行优化. 查看 ...
- EF批量插入数据(Z.EntityFramework.Extensions)
EF用原生的插入数据方法DbSet.ADD()和 DbSet.AddRange()都很慢.所以要做大型的批量插入只能另选它法. 1.Nugget 2.代码 using EF6._0Test.EF; u ...
- 将大量数据批量插入Oracle表的类,支持停止续传
之前用create table select * from XXTable无疑是创建庞大表的最快方案之一,但是数据重复率是个问题,且数据难以操控. 于是我在之前批量插数据的基础上更新了一个类,让它具有 ...
随机推荐
- 更改Visual Studio 2015 默认的语言设置
Vs支持多种语言,但有可能创建项目时,默认的开发语言不是你需要的,比如:默认是Visual C++ 你可以通过"工具"----选项----导入和导出设置来修改. 引用: https ...
- 转载 Android 多线程处理之多线程用法大集合
handler.post(r)其实这样并不会新起线程,只是执行的runnable里的run()方法,却没有执行start()方法,所以runnable走的还是UI线程. 1.如果像这样,是可以操作ui ...
- iOS之百度导航SDK的坐标转换
百度导航 iOS SDK的坐标转换代码示例,有需要的朋友可以参考下. //导航坐标--------------> 地图坐标 //假设从导航sdk取到了一个点坐标是(116.304847, 40. ...
- iOS开发-UI 从入门到精通(二)
iOS开发-UI 从入门到精通(二)是对 iOS开发-UI 从入门到精通(一)知识点的巩固,主要以习题练习为主,增强实战经验,为以后做开发打下坚实的基础! ※开发环境和注意事项: 1.前期iOS-UI ...
- Linux0.11内核--引导程序分析
1.简介 本文主要介绍三个文件bootsect.s.setup.s.head.s,主要是做了些从软盘加载内核和设置32位保护模式的操作. 2.程序分析 当PC电源打开后,BIOS自检后将bootsec ...
- android四大组件(简单总结)
activity 一个Activity通常就是一个单独的屏幕(窗口) Activity之间通过Intent进行通信 android应用中每一个Activity都必须要在AndroidManifest. ...
- git commit之后未submit,rebase之后找不到自己代码的处理方法
今天使用sourceTree提交代码的时候,commit之后未submit,直接rebase主分支代码,完了发现自己本地做的修改都没了,且远程没有本地分支.google之后发现有一个简单方法可以恢复到 ...
- Android 第一http请求访问慢,以后就快了的问题
android的服务端是用MVC+ef,第一次访问特别慢,第一次以后就快了. 在网上找了很多原因,解决不了.后来发现是应用程序池的问题,准确说是ef的问题,应用程序池被回收了,请求就慢了,
- ObjectAnimator.start()工作原理
分析下面一段代码的逻辑 objectAnimator.start(); 他会调用父类的start(),即ValueAnimator,我们分析valueAnimator.start()即可 ValueA ...
- Magical平台类库代码分享
这些天闲来无事,就整理了一些类库.jQuery插件和自定义控件.今天和大家分享下Magical平台类库代码. 下图为整个解决方案图.MagicalPlatForm里面定义的是众多的Layer层:Mag ...