在一个严重多线程的场景中,我遇到了特定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)采样显示线程似乎都花时间在以下方法中:
我有什么遗漏或者我们遇到了一些ADO.NET / SQL Server的角落?
更多背景
运行此查询的代码是Hangfire作业。出于测试目的,脚本将要执行的许多作业排队,最多40个线程继续处理作业。每个作业都使用一个单独的DbContext
实例,并且它并没有真正被大量使用。在有问题的查询之前和之后还有一些查询,它们需要执行预期的时间。
我们为了类似的目的使用了许多不同的Hangfire作业,并且它们的行为符合预期。与此相同,除非在高并发(完全相同的作业)下变慢。此外,只需在此特定查询上切换到SQL即可解决问题。
上面的分析快照是代表性的,所有线程都会减慢此特定方法调用的速度,并将大部分时间花在它上面。
更新
我目前正在为了理智和错误重新运行大量检查。 easy 再现意味着它仍然在远程机器上,我无法使用VS进行连接以进行调试。
其中一项检查显示我之前关于免费CPU的声明是错误的,CPU没有完全过载,但实际上多个核心在长时间运行的整个作业期间都在满负荷运行。
重新检查所有内容,并在此处返回更新。
答案 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
仍然是一个谜,但原始问题已得到解决,剩下的就是优化查询。
非常感谢所有参与者。