EF vs SQL

时间:2016-09-02 15:48:03

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

在一个严重多线程的场景中,我遇到了特定EF查询的问题。它通常便宜又快捷:

Context.MyEntity
  .Any(se => se.SameEntity.Field == someValue        
     && se.AnotherEntity.Field == anotherValue
     && se.SimpleField == simpleValue
     // few more simple predicates with fields on the main entity
     );

这会编译成一个非常合理的SQL查询:

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM   (SELECT [Extent1].[Field1] AS [Field1]
        FROM  [dbo].[MyEntity] AS [Extent1]
        INNER JOIN [dbo].[SameEntity] AS [Extent2] ON [Extent1].[SameEntity_Id] = [Extent2].[Id]
        WHERE (N'123' = [Extent2].[SimpleField]) AND (123 = [Extent1].[AnotherEntity_Id]) AND -- further simple predicates here -- ) AS [Filter1]
    INNER JOIN [dbo].[AnotherEntity] AS [Extent3] ON [Filter1].[AnotherEntity_Id1] = [Extent3].[Id]
    WHERE N'123' = [Extent3].[SimpleField]
)) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

一般来说,查询具有最佳查询计划,使用正确的索引并返回数十个毫秒,这是完全可以接受的。

但是,当临界线程数(< = 40)开始执行此查询时,其上的性能会下降到数十

数据库中没有锁,没有查询将数据写入这些表,并且它可以很好地复制数据库,该数据库实际上与任何其他操作隔离。数据库驻留在同一个物理机器上,机器在任何时候都不会过载,即有足够的CPU,内存和其他资源 此操作会使CPU过载

现在真正奇怪的是,当我使用复制粘贴的SQL(也使用参数)替换带有Any()的EF Context.Database.ExecuteSqlCommand()调用时,问题会神奇地消失。同样,这非常可靠地再现 - 用复制粘贴的SQL替换Any()调用会使性能提高2-3个数量级

附加的探查器(dotTrace)采样显示线程似乎都花时间在以下方法中:

dotTrace sample

我有什么遗漏或者我们遇到了一些ADO.NET / SQL Server的角落?

更多背景

运行此查询的代码是Hangfire作业。出于测试目的,脚本将要执行的许多作业排队,最多40个线程继续处理作业。每个作业都使用一个单独的DbContext实例,并且它并没有真正被大量使用。在有问题的查询之前和之后还有一些查询,它们需要执行预期的时间。

我们为了类似的目的使用了许多不同的Hangfire作业,并且它们的行为符合预期。与此相同,除非在高并发(完全相同的作业)下变慢。此外,只需在此特定查询上切换到SQL即可解决问题。

上面的分析快照是代表性的,所有线程都会减慢此特定方法调用的速度,并将大部分时间花在它上面。

更新

我目前正在为了理智和错误重新运行大量检查。 easy 再现意味着它仍然在远程机器上,我无法使用VS进行连接以进行调试。

其中一项检查显示我之前关于免费CPU的声明是错误的,CPU没有完全过载,但实际上多个核心在长时间运行的整个作业期间都在满负荷运行。

重新检查所有内容,并在此处返回更新。

3 个答案:

答案 0 :(得分:1)

您可以尝试如下所示,看看是否有任何性能提升......

Context.MyEntity.AsNoTracking()
  .Any(se => se.SameEntity.Field == someValue        
     && se.AnotherEntity.Field == anotherValue
     && se.SimpleField == simpleValue
    );

答案 1 :(得分:1)

检查您是否在循环中重用上下文。这样做可能会在性能测试期间创建许多对象,并为垃圾收集器做很多工作。

答案 2 :(得分:1)

错误的初始假设。问题中的SQL是通过将代码粘贴到LINQPad并生成SQL来获得的。

在将SQL事件探查器附加到所使用的实际数据库之后,它显示了一个稍微不同的 SQL,涉及外连接,这是不是最理想的,并且没有适当的索引。

为什么LINQPad生成不同的SQL,即使它使用相同的EntityFramework.dll仍然是一个谜,但原始问题已得到解决,剩下的就是优化查询。

非常感谢所有参与者。