为什么我的Entity Framework查询使用Single方法很慢?

6

我有一个非常简单的查询,但是速度很慢。Entity Framework Profiler 显示它需要大约100毫秒。

dbContext.Users.Single(u => u.Id == userId);

尝试了一番后,我找到了一个非常相似但速度更快的查询(约为3毫秒)。

dbContext.Users.Where(u => u.Id == userId).ToList().Single();

当我比较这两个查询的SQL时,第二个查询没有使用嵌套SELECT和TOP操作。但仅仅因为这两点,我不会期望它会快30倍。同时,在使用SQL Server Management Studio执行这两个查询时,无法测量出任何差异。
当我查看执行计划时,它们都进行了聚集索引查找,其查询成本为100%。而额外的SELECT和TOP操作的查询成本为0%。 EFProfiler的查询计划也是如此,表明这不应该有任何区别。
在这种情况下,我该怎么做才能更好地理解查询性能呢?
下面是第一个查询的SQL结果。
SELECT [Limit1].[Id]                     AS [Id],
   [Limit1].[EmailAddress]           AS [EmailAddress],
   [Limit1].[FirstName]              AS [FirstName],
   [Limit1].[LastName]               AS [LastName]
FROM   (SELECT TOP (2) [Extent1].[Id]                     AS [Id],
                   [Extent1].[EmailAddress]           AS [EmailAddress],
                   [Extent1].[FirstName]              AS [FirstName],
                   [Extent1].[LastName]               AS [LastName]
    FROM   [dbo].[Users] AS [Extent1]
    WHERE  ([Extent1].[Id] = 'b5604f88-3e18-42a5-a45e-c66cc2a632d3' /* @p__linq__0 */)
           AND ('b5604f88-3e18-42a5-a45e-c66cc2a632d3' /* @p__linq__0 */ IS NOT NULL))    AS [Limit1]

以下是第二个(更快)查询的SQL代码。

SELECT [Extent1].[Id]                     AS [Id],
   [Extent1].[EmailAddress]           AS [EmailAddress],
   [Extent1].[FirstName]              AS [FirstName],
   [Extent1].[LastName]               AS [LastName]
FROM   [dbo].[Users] AS [Extent1]
WHERE  ([Extent1].[Id] = 'b5604f88-3e18-42a5-a45e-c66cc2a632d3' /* @p__linq__0 */)
   AND ('b5604f88-3e18-42a5-a45e-c66cc2a632d3' /* @p__linq__0 */ IS NOT NULL)

1
你也可以在第二个示例中省略ToList()调用。Single()函数同样会对查询进行枚举,只需保留Where()子句即可。 - Robert Koritnik
5
.Single()方法需要返回“前两个”结果以确定是否有单个匹配。结果中的一行表示成功,两行或零行表示“.Single()”失败并应抛出错误。在您的第二个示例中,“.ToList()”会导致查询在应用“.Single()”之前执行。查询可以返回任意数量的结果,并且.Single()方法将应用于List结果。 - Grax32
1
你是否在同一个应用程序中尝试这两个查询?如果是的话,较慢的那个查询是否是 Entity Framework 在应用程序中执行的第一个查询?Entity Framework 对每个数据库每个应用程序域执行的第一个查询有很大的开销,它会重复使用构建的缓存元数据,即使对象已经被释放,但是那个非常慢的第一个查询将会慢得多(只需在程序后面再次运行相同的查询,你就会看到速度上的显著变化)。 - Scott Chamberlain
1
@Towa 我很惊讶在 GUID 上创建的聚集索引没有降低性能。 - Jaycee
2
@Jaycee:很可能是这样,但也有可能是他的表很小,所以根本没有或者很少分页... GUID不适合作为主键... - Robert Koritnik
显示剩余6条评论
2个回答

2
你实际需要的是dbContext.Users.Find(id) - 如果不必要,这甚至不会访问数据库。在msdn上可以查看更多细节。

+1 告诉我关于 Find() 的事情。我之前不知道这个。但在这种特殊情况下,它生成与 Single 相同的 SQL,并且具有相同(糟糕)的性能。 - Kuepper

0

当你说:

dbContext.Users.Single(u => u.Id == userId);

Users 是 DbSet 类型或者是用户的集合,因此它首先获取用户集合,而在
dbContext.Users.Where(u => u.Id == userId).ToList().Single(); 中包含了加载条件。

所以如果有 100 个用户,第一个查询将获取 100 个用户,然后进行过滤,而第二个查询只会获取 1 个用户。

希望这可以帮助到您。


3
Anshul,第一个查询仅返回与该用户ID匹配的数据库中前两个用户,即使有100个具有该用户ID的用户也是如此。第二个查询将返回与该用户ID匹配的所有用户,然后在内存中应用单一方法。 - Julie Lerman
为什么不在StackOverflow上开一个新的线程来提问呢? :) - Julie Lerman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接