EF vs SQL的好奇慢

Jac*_*goń 11 c# sql-server ado.net multithreading entity-framework

在一个高度多线程的场景中,我遇到了特定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
     );
Run Code Online (Sandbox Code Playgroud)

这编译成一个非常合理的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]
Run Code Online (Sandbox Code Playgroud)

通常,查询具有最佳查询计划,使用正确的索引并以几十毫秒的速度返回,这是完全可以接受的.

然而,当线程的临界数(<= 40)开始执行该查询,在其上的性能下降到几十秒钟.

数据库中没有锁,没有查询将数据写入这些表,并且它与使用几乎与任何其他操作隔离的数据库重现良好.DB驻留在同一台物理机器上,机器在任何时候都不会过载,即CPU有大量备用CPU,内存和其他资源 ,CPU会因此操作而过载.

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


附加的分析器(dotTrace)示例显示线程似乎都花时间在以下方法中:

dotTrace示例

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


更多背景

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

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

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


UPDATE

我目前正在重新运行大量的检查以获得理智和错误.在简单的再现意味着它仍然是一个远程机器,我可以不使用VS进行调试连接上.

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

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

Jac*_*goń 1

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

将 SQL 探查器附加到实际使用的数据库后,它显示了涉及外连接的稍微不同的SQL,这是次优的并且没有适当的索引。

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

非常感谢所有参与人员。