我一直在调试一些慢速代码,似乎罪魁祸首是下面发布的EF代码.在稍后阶段评估查询时需要4-5秒.我试图让它在1秒内运行.
我使用SQL Server Profiler对此进行了测试,似乎执行了一堆SQL脚本.它还确认SQL Server完成执行需要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(); }
Gert Arnold.. 57
使用Include时似乎确实存在性能损失
这是轻描淡写!多个Include
s在宽度和长度上快速爆炸SQL查询结果.这是为什么?
tl; dr Multiple Include
s炸掉了SQL结果集.很快,通过多个数据库调用加载数据而不是运行一个mega语句变得更便宜.尝试找到最好的混合Include
和Load
陈述.
Include
s让我们说我们有
根实体 Root
父实体 Root.Parent
子实体Root.Children1
和Root.Children2
一个LINQ语句 Root.Include("Parent").Include("Children1").Include("Children2")
这将构建一个具有以下结构的SQL语句:
SELECT *,
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *,
FROM Root
JOIN Parent
JOIN Children2
它们
由类似的表达式组成,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
(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在宽度和长度上快速爆炸SQL查询结果.这是为什么?
tl; dr Multiple Include
s炸掉了SQL结果集.很快,通过多个数据库调用加载数据而不是运行一个mega语句变得更便宜.尝试找到最好的混合Include
和Load
陈述.
Include
s让我们说我们有
根实体 Root
父实体 Root.Parent
子实体Root.Children1
和Root.Children2
一个LINQ语句 Root.Include("Parent").Include("Children1").Include("Children2")
这将构建一个具有以下结构的SQL语句:
SELECT *,
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *,
FROM Root
JOIN Parent
JOIN Children2
它们
由类似的表达式组成,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
(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
语句最适合您.