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

科技网编2023-09-28 17:011560

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

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

可能需要测试多次往返数据库的组合的性能,并且Contains每次往返表达式中的元素数量较少。

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

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

在这种情况下,原始sql命令可能会发挥最佳性能,这意味着您将调用@Rune答案中显示的sqldbContext.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 code snippets from below

    watch.Stop();
    var msec = watch.ElapsedMilliseconds;
}
var result = context.Set<MyEntity>()
    .Where(e => ids.Contains(e.ID))
    .ToList();

结果->

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

结果->

的这种微小影响AsNoTracking非常不寻常。它表明瓶颈不是对象实现(而不是如下所示的sql)。

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

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();

结果->

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

结果->

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

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

结果->

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

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

即使是数据库的20000次往返也比使用Contains以下方法更快:

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

结果->

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

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

解决方法

通过主键选择多个实体的最有效方法是什么?

public IEnumerable<Models.Image> GetImagesById(IEnumerable<int> ids)
{

    //return ids.Select(id => Images.Find(id));       //is this cool?
    return Images.Where( im => ids.Contains(im.Id));  //is this better,worse or the same?
    //is there a (better) third way?

}

我意识到我可以进行一些性能测试以进行比较,但是我想知道实际上是否有比这两种方法更好的方法,并且我想对这两个查询(如果有)之间的区别(如果有)有所启发“翻译”。

总结

以上是真正的电脑专家为你收集整理的为什么。包含缓慢?通过主键获取多个实体的最有效方法?的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得真正的电脑专家网站内容还不错,欢迎将真正的电脑专家推荐给好友。

评论区