我一直在调试一些缓慢的代码,似乎罪魁祸首是下面发布的 EF 代码。在稍后阶段评估查询需要 4-5 秒。我试图让它在 1 秒内运行。
我已经使用 SQL Server Profiler 对此进行了测试,似乎执行了一堆 SQL 脚本。它还确认在 SQL 服务器完成执行之前需要 3-4 秒。
我已经阅读了有关使用 Include() 的其他类似问题,并且在使用它时似乎确实存在性能损失。我尝试将以下代码拆分为几个不同的查询,但没有太大区别。
知道如何让以下内容更快地执行吗?
目前,我正在开发的网络应用程序在等待以下完成时只显示一个空的 iframe。如果我无法获得更快的执行时间,我必须将其拆分并使用数据部分加载 iframe 或使用另一个异步解决方案。这里的任何想法也将不胜感激!
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
{
formInstance = context.FormInstanceSet
.Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormSectionDefinitions).Include(fs => fs.FormStateDefinitionEditableSections))
.Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormStateDefinitions))
.Includes(x => x.Include(fi => fi.FormSectionInstances).Include(fs => fs.FormFieldInstances).Include(ff => ff.FormFieldDefinition).Include(ffd => ffd.FormFieldMetaDataDefinition).Include(ffmdd => ffmdd.ComplexTypePropertyNames))
.Include(x => x.CurrentFormStateInstance)
.Include(x => x.Files)
.FirstOrDefault(x => x.FormInstanceIdentifier == formInstanceIdentifier);
scope.Complete();
}
最佳答案
tl;博士 多个 Include
s 炸毁 SQL 结果集。很快,通过多个数据库调用加载数据而不是运行一个大型语句会变得更便宜。尝试找到 Include
的最佳组合和 Load
声明。
it does seem that there is a performance penalty when using Include
这是轻描淡写!多个
Include
s 迅速在宽度和长度上炸毁 SQL 查询结果。这是为什么?Include
增长因子秒(这部分适用于 Entity Framework classic、v6 及更早版本)
假设我们有
Root
Root.Parent
Root.Children1
和 Root.Children2
Root.Include("Parent").Include("Children1").Include("Children2")
这将构建一个具有以下结构的 SQL 语句:
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2
这些 <PseudoColumns>
由类似 CAST(NULL AS int) AS [C2],
的表达式组成并且它们在所有 UNION
中具有相同数量的列-ed 查询。第一部分为 Child2
添加伪列, 第二部分为 Child1
添加伪列.这就是 SQL 结果集大小的含义:
SELECT
中的列数子句是四个表中所有列的总和由于数据点总数为
columns * rows
, 每增加一个 Include
以指数方式增加结果集中的数据点总数。让我通过 Root
来证明这一点再次,现在有一个额外的 Children3
收藏。如果所有表都有 5 列和 100 行,我们得到:一
Include
( Root
+ 1 个子集合):10 列 * 100 行 = 1000 个数据点。两个
Include
s(Root
+ 2 个子集合):15 列 * 200 行 = 3000 个数据点。三
Include
s(Root
+ 3 个子集合):20 列 * 300 行 = 6000 个数据点。与 12
Includes
这将达到 78000 个数据点!相反,如果分别获取每个表的所有记录而不是 12
Includes
, 你有 13 * 5 * 100
数据点:6500,不到10%!现在这些数字有些夸大了,因为这些数据点中有许多是
null
,因此它们对发送到客户端的结果集的实际大小贡献不大。但是查询优化器的查询大小和任务肯定会因 Include
的数量增加而受到负面影响。 s。平衡
所以使用
Includes
是数据库调用成本和数据量之间的微妙平衡。很难给出一个经验法则,但现在您可以想象,如果超过 ~3 Includes
,数据量通常很快就会超过额外调用的成本。对于子集合(但对于父集合 Includes
多得多,这只会扩大结果集)。选择
Include
的替代方案是在单独的查询中加载数据:context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);
这会将所有需要的数据加载到上下文的缓存中。在此过程中,EF 执行关系修复,通过加载的实体自动填充导航属性(Root.Children
等)。最终结果与带有 Include
的语句相同s,除了一个重要的区别:子集合在实体状态管理器中没有标记为已加载,因此如果您访问它们,EF 将尝试触发延迟加载。这就是为什么关闭延迟加载很重要的原因。实际上,您必须弄清楚
Include
的哪个组合和 Load
陈述最适合你。其他需要考虑的方面
每个
Include
还增加了查询的复杂性,因此数据库的查询优化器将不得不越来越多地努力寻找最佳查询计划。在某些时候,这可能不再成功。此外,当某些重要索引丢失(尤其是外键)时,添加 Include
可能会影响性能。 s,即使有最好的查询计划。Entity Framework 核心
笛卡尔爆炸
出于某种原因,上述行为,UNIONed 查询,从 EF 核心 3 开始被放弃。它现在构建一个带有连接的查询。当查询为“星形”1 时,这会导致笛卡尔爆炸(在 SQL 结果集中)。我只能找到一个 note announcing this breaking change ,但没有说明原因。
拆分查询
为了应对这种笛卡尔爆炸,Entity Framework core 5 引入了 split queries 的概念。允许在多个查询中加载相关数据。它可以防止构建一个庞大的、成倍增加的 SQL 结果集。此外,由于较低的查询复杂性,即使多次往返,它也可以减少获取数据所需的时间。但是,并发更新时可能会导致数据不一致。
1 查询根之外的多个 1:n 关系。
关于c# - 多次使用 Include() 时 Entity Framework 代码很慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34724196/