关于EF Core 更新速度随时间越来越慢的解决办法
关于EF Core更新速度随时间越来越慢的解决办法
概要
本篇主要介绍使用 context.ChangeTracker.Clear() 方法,在通过循环进行批量更新时,通过手动清除跟踪实体以提高性能的示例。
背景
最近在做一些数据分析时,遇到了一个问题,当我把计算结果更新到数据库时,一开始速度会很快,但随着时间的推移,更新速度会越来越慢。
本篇博客就来说明这种现象的原因和解决办法。
环境:
ASP.NET Core 7和EF Core 7.
事例说明
我有1000W已处理好的数据需要更新到数据库,这些数据我也是从数据库中一次性查询出来的,这样可以只进行一次查询,并使用AsNoTracking()提高查询效率,然后我对这些数据进行了并行计算,最后将计算完的结果更新到数据库。最费时的操作就是更新到数据库。
请看以下代码示例:
var bc = new ConcurrentBag<List<StockDailyKLineInfo>>();
// 并行计算
var computeTasks = group.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.Select(async g =>
{
var computedData = await service.ComputeAsync(g.ToList());
if (computedData != null)
{
bc.Add(computedData);
}
});
await Task.WhenAll(computeTasks);
// 数据插入
var batchSize = 5000;
var items = bc.SelectMany(x => x).ToList();
left = items.Count;
_logger.LogInformation($"need update {left} daily!");
foreach (var batch in items.Chunk(batchSize))
{
context.AttachRange(batch);
foreach (var entity in batch)
{
var entry = context.Entry(entity);
entry.Property(e => e.A).IsModified = true;
entry.Property(e => e.B).IsModified = true;
entry.Property(e => e.C).IsModified = true;
entry.State = EntityState.Modified;
}
var count = await context.SaveChangesAsync();
}
await Console.Out.WriteLineAsync("[done] update all data");
并行计算速度非常快,几秒就能都完成了。
数据插入,我分批进行循环插入,每次5000条,通常不到1秒时间就能插入成功。但随着时间的推移,插入速度越来越慢。
[!NOTE]
由于我有1000W的数据插入,如果最终一次性提交,如果出现了异常,那么所有数据都不会插入成功,并且会等待很长的时间,并且在最终执行完成之前,你得不到任何信息,以预估可能花费的时间。所以我需要分批插入。
原因
EF Core 会在上下文中跟踪所有已加载或附加的实体。随着循环的进行,上下文将追踪越来越多的实体,这可能会导致性能下降。
也就是说在同一个DbContext上下文中,SaveChangesAsync()方法调用后,不会清除已更新的内容,这意味着追踪的实体越来越多,最终多达1000W,并且这些都是已经标记为要更新的内容,也意味着你每次都会更新更多的内容到数据库。
解决办法
只进行一次SaveChanges
既然每次saveChanges不会清除,那么最后我只提交一次不就行了么?但这个方案不符合实际需求,上面已经提到过了。
使用多个DbContext
既然 同一个DbContext下会出现这个问题,那么每次更新,我再创建一个新的DbContext不就可以了么?
这个方法虽然可行,但对于1000W的数据来说,即使我每次更新1W条数据,也需要创建1000+次DbContext,也有一定的消耗。
清除追踪
既然问题是SaveChanges不会自动清除已追踪的更改,如果我可以手动去清除,不就可以了么?清除的操作比起创建新的DbContext实例,还是更快捷的。
那么我们修改代码:
foreach (var batch in items.Chunk(batchSize))
{
context.AttachRange(batch);
foreach (var entity in batch)
{
var entry = context.Entry(entity);
entry.Property(e => e.A).IsModified = true;
entry.Property(e => e.B).IsModified = true;
entry.Property(e => e.C).IsModified = true;
entry.State = EntityState.Modified;
}
var count = await context.SaveChangesAsync();
// ⚒️ add this line
context.ChangeTracker.Clear();
}
[!TIP]
context.ChangeTracker.Clear()方法清除上下文中的所有已跟踪实体。这将重置更改跟踪器并清除其跟踪的所有实体,从而释放内存并提高性能。
总结
EF Core 7 中已经添加了批量更新的方法,但这种方法也不适用于我遇到的场景,因为我不是按条件进行批量更新,而是每一条数据都需要更新。
context.ChangeTracker.Clear()可以在这样的场景下发挥作用,在一些关联插入或更新的场景,为避免追踪带来的冲突问题,也可以通过该方法清除追踪,然后再手动建立关系,进行提交。
关于EF Core 更新速度随时间越来越慢的解决办法的更多相关文章
- .NET 云原生架构师训练营(模块二 基础巩固 EF Core 更新和迁移)--学习笔记
2.4.6 EF Core -- 更新 状态 自动变更检测 不查询删除和更新 并发 状态 Entity State Property State Entity State Added 添加 Uncha ...
- Ubuntu 14.04—无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系 解决办法
在Ubuntu中使用sudo apt-get install安装是有时候会出现: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系 解决办法 这样的错误,这是因为更新源 ...
- C#项目间循环引用的解决办法,有图有真相
C#项目间循环引用的解决办法,有图有真相 程序间的互相调用接口,c#禁止互相引用,海宏软件,20160315 /// c#禁止互相引用,如果项目[订单]中有一个orderEdit单元,要在项目[进销存 ...
- mongodb 更新数据时int32变为double的解决办法 & 教程
https://www.runoob.com/mongodb/mongodb-mongodump-mongorestore.html mongodb 更新数据时int32变为double的解决办法 ...
- 使用EF Core更新与修改生产数据库
使用EF Core的Code First,在设计阶段,直接使用Database.EnsureCreated()和EnsureDeleted()可以快速删除.更新最新的数据结构.由于没有什么数据,删除的 ...
- mongodb 更新数据时int32变为double的解决办法
场景: 在命令手动的修改签到表的整型字段synState,multi参数是可以更新多条,如果是false则更新一条. db.getCollection("ClassRecordOneD ...
- myeclipse工程更新后java图标变为空心的解决办法
今天用svn更新了工程发现目录结构改变了,同时所有的java文件的图标变成了空心的.解决办法如下 1.右键单击工程目录名,选择properties. 2.选择java bulid path,正常的应该 ...
- EF 表中中多次指定了列名解决办法
这个问题是我们实际开发中遇到过的问题. 可能的原因:数据库在执行数据表迁移的时候,数据表执行成功,最后插入EF数据迁移表__MigrationHistory的时候,没有把所有的命令行完整插入,缺失了一 ...
- 关于访问Jira和Confluence服务越来越缓慢的解决办法阐述
Jira和Confluence部署在同一台服务器上,跑一段时间后,发现访问jira和confluence时,打开越来越缓慢.这是因为根据主机物理内存不同,默认的java虚拟机内存也会不同(一个较低值) ...
- setTimeout代替setInterval的写法以及setInterval的弊端以及越来越快的解决办法
平常经常遇到的一个问题,很多人想间隔时间执行一些事件的时候,第一时间就会想到用setInterval,但是setInterval村子啊不少弊端哦. 弊端1:setInterval会无视错误代码,即使代 ...
随机推荐
- OpenHarmony社区运营报告(2023年6月)
本月快讯 • 6月12日,以"OpenHarmony共建开放,共享未来"为主题的2023开放原子全球开源峰会OpenAtom OpenHarmony(以下简称"Ope ...
- Rome反序列化链分析
环境搭建 <dependencies> <dependency> <groupId>junit</groupId> <artifactId> ...
- 详解Java Chassis 3与Spring Cloud的互操作
本文分享自华为云社区<Java Chassis 3技术解密:与Spring Cloud的互操作>,作者: liubao68. Java Chassis 3一个很重要的设计原则:利用架构的韧 ...
- MindSpore编译构建后Vmap模块的RuntimeError问题
技术背景 这篇文章来源于MindSpore仓库中的一个Issue,简单描述问题就是,如果你用MindSpore开发了一个python软件供别人使用,那么很有可能涉及到编译构建的问题.但是如果直接使用编 ...
- Android 开发入门(2)
0x04 简单控件 (1)文本显示 a. 添加文本 设置文本内容主要有两种方式: XML:通过属性android:text设置 在 layout 目录下新建 activity_text_view.xm ...
- input 去除默认样式
前言 如何不自己写框架,基本用不上. 正文 input{ border: 0px; background-color: none; outline: none; } input:focus{ outl ...
- leetcode:1381. 设计一个支持增量操作的栈
1381. 设计一个支持增量操作的栈 请你设计一个支持下述操作的栈. 实现自定义栈类 CustomStack : CustomStack(int maxSize):用 maxSize 初始化对象,ma ...
- 如何快速实现Prometheus监控Kubernetes集群
Prometheus K8S集群中常见的监控工具有哪些: Kubernetes Dashboard Kube-monkey K8s-testsuite Kubespray Minikube Prome ...
- 力扣1112(MySQL)-每位学生的最高成绩(中等)
题目: 表:Enrollments (student_id, course_id) 是该表的主键. 问题编写一个 SQL 查询,查询每位学生获得的最高成绩和它所对应的科目,若科目成绩并列,取 cour ...
- 力扣591(java)-标签验证器(困难)
题目: 给定一个表示代码片段的字符串,你需要实现一个验证器来解析这段代码,并返回它是否合法.合法的代码片段需要遵守以下的所有规则: 代码必须被合法的闭合标签包围.否则,代码是无效的. 闭合标签(不一定 ...