【问题标题】:Extremely slow and inefficient query execution from Entity Framework来自实体框架的查询执行速度极慢且效率低下
【发布时间】:2015-03-24 20:41:07
【问题描述】:

我在 Windows 2008R2 的 ASP.NET 上运行了带有 .NET 4.5 的 Entity Framework 4.1。我使用 EF 代码优先连接到 SQL Server 2008R2,并执行一个相当复杂的 LINQ 查询,但结果只是一个 Count()

我已经在两台不同的网络服务器上重现了这个问题,但只有一个数据库(当然是生产)。它最近开始发生,在 Web 或数据库端没有应用程序、数据库结构或服务器更改。

我的问题是在某些情况下执行查询会花费大量时间(接近 4 分钟)。我可以从 SQL Profiler 中提取实际查询,并在大约 1 秒内在 SSMS 中执行。这对我来说是一致且可重现的,但是如果我将其中一个参数(“2015-01-22 之后的日期”参数)的值更改为更早的值,例如 2015-01-01,或者更晚的值,例如 2015-02- 01,它在EF中运行良好。但我把它放回 2015-01-22 又变慢了。我可以一遍又一遍地重复。

然后我可以在 EF 中运行一个类似但不相关的查询,然后返回到原始查询,这次它运行良好 - 与以前完全相同的查询。但是如果我打开一个新的浏览器,循环又会重新开始。这部分也没有任何意义——我们没有做任何事情来保留用户会话中的数据上下文,所以我不知道为什么会这样。

但这一切都告诉我数据本身是好的。

在 Profiler 中,当查询正常运行时,大约需要一两秒,并显示大约 2,000,000 次读取和大约 2,000 次 CPU。当它运行缓慢时,需要 3.5 分钟,并且值是 300,000,000 和 200,000 - 所以读取量大约高出 150 倍,CPU 高出 100 倍。同样,对于相同的 SQL 语句。

关于 EF 可能会做哪些不同但不会出现在查询文本中的任何建议?是否存在某种隐藏的连接属性,在某些情况下可能会导致不同的执行计划?

编辑

EF 构建的查询是其中一种使用包含在文本中的参数而不是 SQL 参数来构建巨型字符串的查询:

exec sp_executesql 
   N'SELECT [GroupBy1].[A1] AS [C1] 
     FROM ( 
          SELECT COUNT(1) AS [A1]
           ...
           AND ([Extent1].[Added_Time] >= convert(datetime2, ''2015-01-22 00:00:00.0000000'', 121)) 
           ...
           ) AS [GroupBy1]'

编辑

我没有将其添加为答案,因为它实际上并没有解决根本问题,但这最终通过重建索引和重新计算统计信息得到解决。这并没有比平时更久,而且似乎已经解决了导致问题的任何原因。

我会继续阅读这里的一些链接,以防再次发生这种情况,但由于现在一切正常且无法重现,我不知道我是否能确切知道它在做什么。

感谢所有的想法。

【问题讨论】:

  • 这对我来说听起来像是错误的参数嗅探。查看有关该主题的这篇文章。 sqlinthewild.co.za/index.php/2007/11/27/parameter-sniffing(确保您阅读了所有三个部分)
  • @Guvante 我很想在 SSMS 中重现,但无论我做什么,它总是从那里运行良好。这也让 DBA 更难提供帮助,因为他们无法重现它。
  • @SergueiFedorov 似乎总体上可以很好地扩展。在 SSMS 中,当应用程序运行时,查询运行良好。我们在主表中有几十万条记录,即使在无限日期范围内执行此查询,它也会在几秒钟内返回。
  • 可能有用:Slow in the Application, Fast in SSMS? Understanding Performance Mysteries。 SSMS 使用与 ADO.NET 不同的查询选项,因此它可能不会使用相同的缓存查询计划,这解释了为什么您无法重现 SSMS 的减速。您是否尝试过更新统计信息? (链接的文章还有其他故障排除建议。)
  • @Ewan 你不会在 10 毫秒内获得 200 万次读取。检查丢失的索引可能更重要...

标签: c# asp.net sql-server entity-framework


【解决方案1】:

我最近遇到了一个非常相似的场景,一个查询会运行得非常快,直接在数据库中执行它,但使用 EF(在我的例子中是第 5 版)时性能很差。不是网络问题,相差4ms到10分钟。

问题最终变成了映射问题。我有一列映射到NVARCHAR,而它在数据库中是VARCHAR。看似无害,但这会导致数据库中的隐式转换,这完全破坏了性能。

我不完全确定为什么会发生这种情况,但根据我所做的测试,这导致数据库执行 Index Scan 而不是 Index Seek,显然它们在性能方面非常不同。

我在博客上写过这个 here(免责声明:它是葡萄牙语),但后来我发现 Jimmy Bogard 在 2012 年的 post 中描述了这个确切的问题,我建议你看看。

由于您的查询中确实有转换,我会说从那里开始。仔细检查所有列映射并检查表列和实体属性之间的差异。避免在查询中进行隐式转换。
如果可以,请检查您的 execution plan 以查找任何不一致之处,请注意黄色警告三角形,它可能表示有关执行隐式转换的问题:

我希望这对您有所帮助,这对我们来说是一个非常困难的问题,但最终还是有意义的。

【讨论】:

  • 那是列映射的黄金信息。谢谢!
【解决方案2】:

只是把它放在那里,因为它没有被认为是一种可能性:

鉴于您使用的是实体框架 (EF),如果您使用的是实体的延迟加载,则 EF 需要通过连接字符串启用多个活动结果集 (MARS)。虽然看起来完全不相关,但 MARS 有时确实会产生这种在 SSMS 中快速运行但通过 EF 慢得可怕(几秒钟变成几分钟)的行为。

对此进行测试的一种方法是关闭延迟加载并删除MultipleActiveResultSets=True;(默认为“false”)或至少将其更改为MultipleActiveResultSets=False;

据我所知,遗憾的是(目前)没有解决方法或修复此行为。

这里是这个问题的一个例子:Same query with the same query plan takes ~10x longer when executed from ADO.NET vs. SMSS

【讨论】:

  • 有趣,谢谢 - 这里不是一个因素,因为它取决于单个单值查询,所以 MARS 没有发挥作用。但这确实给了我另一个警惕实体框架的理由。
【解决方案3】:

有一篇关于实体框架性能考虑的优秀文章here

我想提请您注意关于冷查询与热查询执行的部分:

第一次对给定模型进行任何查询时, 实体框架在后台做了很多工作来加载和 验证模型。我们经常将第一个查询称为 “冷”查询。针对已加载模型的进一步查询是 称为“暖”查询,并且速度更快。

在 LINQ 查询执行期间,“元数据加载”步骤对冷查询执行的性能有很大影响。但是,一旦加载的元数据将被缓存,未来的查询将运行得更快。元数据缓存在 DbContext 之外,只要应用程序池存在,就可以重用。

为了提高性能,请考虑以下操作:

  • 使用预先生成的视图
  • 使用查询计划缓存
  • 不使用跟踪查询(仅在以只读方式访问时)
  • 创建实体框架的本机映像(仅在使用 EF 6 或更高版本时相关)

所有这些点都在上面提供的链接中有详细记录。此外,您还可以找到有关创建实体框架here 的本机映像的其他信息。

【讨论】:

  • 似乎不符合所描述的行为(它在同一模型上的其他日期工作正常,并且在同一日期重复糟糕地消除了启动成本)。对我来说,这听起来像是一个糟糕的缓存执行计划。
  • 这些都不需要 4 分钟的时间。
  • 感谢您的信息 - 如果只是几秒钟,我同意它可能与加载模型有关,但由于它是几分钟,并且一遍又一遍地发生,并且 SQL Profiler结果是如此可笑的不同,我倾向于执行实际查询而不是内部 EF 加载的某种问题。
  • 我错过了读取信息。那么绝对是执行计划问题。
【解决方案4】:

我没有关于为什么会发生这种情况的具体答案,但它肯定看起来与查询的处理方式有关,而不是与查询本身有关。如果您说从 SSMS 运行相同的生成查询没有任何问题,那么这不是问题。

您可以尝试的解决方法:存储过程。 EF 可以很好地处理它们,它是处理可能复杂或昂贵的查询的理想方式。

【讨论】:

  • 我很想切换到 procs 或常规 SQL,但正如我所提到的,这是一个庞大的 EF LINQ 查询(根据您搜索的内容添加了许多动态子句),所以这是一项艰巨的任务,我还没有准备好。
【解决方案5】:

意识到您正在使用 Entity Framework 4.1,我建议您升级到 Entity Framework 6。

性能有了很多改进,EF 6 比 EF 4.1 快得多。

我其他回复中提到的MSDN article about Entity Framework performance consideration也有EF 4.1和EF 6的对比。

因此可能需要进行一些重构,但性能的改进应该是值得的(同时会减少技术债务)。

【讨论】:

  • 这与问题无关。它可以作为 Stack Overflow 上大约 1000 个问题的答案发布。
  • 我可能会考虑升级,或者最好完全放弃 EF,但正如您所说,这将需要大量的重构和测试。
  • @usr:它可能最终不会成为公认的答案,但我发现在出现性能问题时对建议升级到最新版本 EF 的答案投反对票有点苛刻。这是我要做的第一件事。
  • @AlexSanséau:可能是这样,但这与问题的关系不大。充其量只是一个评论。
  • 这不是一个好的答案。事实上,您没有充分理由针对这个问题发布两个答案。
猜你喜欢
  • 1970-01-01
  • 2017-08-29
  • 2011-07-24
  • 1970-01-01
  • 2013-01-30
  • 2016-02-29
  • 2018-04-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多