为什么。包含缓慢?通过主键获取多个实体的最有效方法?

为什么。包含缓慢?通过主键获取多个实体的最有效方法?,第1张

为什么。包含缓慢?通过主键获取多个实体的最有效方法?

更新:通过在EF6中添加Inexpression,处理Enumerable.Contains的性能得到了显着提高。
自2013年以来,此答案中的分析很棒,但已经过时了。

使用

Contains
实体框架其实是很慢的。的确,它可以转换为
IN
SQL中的子句,并且SQL查询本身可以快速执行。但是问题和性能瓶颈在于从LINQ查询到SQL的转换。将要创建的表达式树被扩展为一长串
OR
串联,因为没有原生表达式表示
IN
。创建SQL时,
OR
可以识别出许多s的表达式并将其折叠回SQL
IN
子句中。

这并不意味着使用

Contains
比在
ids
集合中的每个元素上发出一个查询(您的第一个选择)更糟糕。它可能仍然更好-
至少对于不是太大的收藏。但是对于大型收藏来说,这确实很糟糕。我记得我曾经测试过一个
Contains
包含大约12.000个元素的查询,该查询可以工作,但是花费了大约一分钟,即使SQL中的查询在不到一秒钟的时间内执行了。

可能需要测试多次往返数据库的组合的性能,并且

Contains
每次往返表达式中的元素数量较少。

Contains
此处显示并说明了这种方法以及与Entity framework一起使用的局限性:

为什么Contains()运算符会如此大幅度降低Entity
framework的性能?

在这种情况下,原始SQL命令可能会发挥最佳性能,这意味着您将调用@Rune答案中显示的SQL

dbContext.Database.SqlQuery<Image>(sqlString)
dbContext.Images.SqlQuery(sqlString)
在哪里
sqlString

编辑

以下是一些测量:

我在具有550000条记录和11列的表上完成了此 *** 作(ID从1开始没有间隙),并随机选择了20000个ID:

using (var context = new MyDbContext()){    Random rand = new Random();    var ids = new List<int>();    for (int i = 0; i < 20000; i++)        ids.Add(rand.Next(550000));    Stopwatch watch = new Stopwatch();    watch.Start();    // here are the pre snippets from below    watch.Stop();    var msec = watch.ElapsedMilliseconds;}

测试1

var result = context.Set<MyEntity>()    .Where(e => ids.Contains(e.ID))    .ToList();

结果-> 毫秒= 85.5秒

测试2

var result = context.Set<MyEntity>().AsNoTracking()    .Where(e => ids.Contains(e.ID))    .ToList();

结果-> 毫秒= 84.5秒

的这种微小影响

AsNoTracking
非常不寻常。它表明瓶颈不是对象实现(而不是如下所示的SQL)。

对于这两个测试,可以在SQL
Profiler中看到SQL查询很晚才到达数据库。(我没有进行精确测量,但是它晚了70秒。)显然,将LINQ查询转换为SQL的代价非常高。

测试3

var values = new StringBuilder();values.AppendFormat("{0}", ids[0]);for (int i = 1; i < ids.Count; i++)    values.AppendFormat(", {0}", ids[i]);var sql = string.Format(    "SELECt * FROM [MyDb].[dbo].[MyEntities] WHERe [ID] IN ({0})",    values);var result = context.Set<MyEntity>().SqlQuery(sql).ToList();

结果-> 毫秒= 5.1秒

测试4

// same as Test 3 but this time including AsNoTrackingvar result = context.Set<MyEntity>().SqlQuery(sql).AsNoTracking().ToList();

结果-> 毫秒= 3.8秒

这次,禁用跟踪的效果更加明显。

测试5

// same as Test 3 but this time using Database.SqlQueryvar result = context.Database.SqlQuery<MyEntity>(sql).ToList();

结果-> 毫秒= 3.7秒

我的理解是

context.Database.SqlQuery<MyEntity>(sql)
与相同
context.Set<MyEntity>().SqlQuery(sql).AsNoTracking()
,因此在测试4和测试5之间没有预期的区别。

(由于随机id选择后可能重复,结果集的长度并不总是相同的,但始终在19600和19640个元素之间。)

编辑2

测试6

即使是数据库的20000次往返也比使用

Contains
以下方法更快:

var result = new List<MyEntity>();foreach (var id in ids)    result.Add(context.Set<MyEntity>().SingleOrDefault(e => e.ID == id));

结果-> 毫秒= 73.6秒

请注意,我使用

SingleOrDefault
代替
Find
。使用相同的代码
Find
非常慢(几分钟后我取消了测试),因为内部
Find
调用了
DetectChanges
。禁用自动更改检测(
context.Configuration.AutoDetectChangesEnabled= false
)可获得与大致相同的性能
SingleOrDefault
。使用
AsNoTracking
可以将时间减少一到两秒。

测试是在同一台计算机上使用数据库客户端(控制台应用程序)和数据库服务器完成的。由于有许多往返,使用“远程”数据库的最后结果可能会变得更糟。



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5567217.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存