21

为什么强制实现使用ToList()会使我的查询速度提高几个数量级,如果有的话,它应该做完全相反的事情?

First()1)立即调用

    // "Context" is an Entity Framework DB-first model

    var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

    var User = query.First();

    //  ** The above takes 30+ seconds to run **

2)调用First() 调用ToList()

    var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

    var User = query.ToList().First();     // Added ToList() before First()

    // ** Now it takes < 1 second to run! **

更新和解决

得到生成的 SQL 后,唯一的区别是,不出所料,TOP (1)在第一个查询中添加了。正如Andyz Smith在下面的回答中所说,根本原因是在这种特殊情况下,SQL Server 优化器在TOP (1)添加时选择了更差的执行计划。因此,问题与 LINQ 无关(它通过添加 做了正确的事情TOP (1))以及与 SQL Server 的特性有关的一切。

4

3 回答 3

11

我只能想到一个原因...要测试它,您可以删除该Where子句并重新运行测试吗?如果结果是第一个语句更快,请在此处评论,我将解释原因。

编辑
在 LINQ 语句 Where 子句中,您使用的是字符串的 .ToLower() 方法。我的猜测是 LINQ 没有内置转换为这种方法的 SQL,所以生成的 SQL 是行

SELECT *
FROM Users

现在,我们知道 LINQ 延迟加载,但它也知道,由于它没有评估WHERE子句,它需要加载元素来进行比较。

假设
第一个查询是延迟加载结果集中的每个元素。然后它进行 .ToLower() 比较并返回第一个结果。这会导致n对服务器的请求和巨大的性能开销。没有看到 SQL Tracelog 就无法确定。

第二条语句调用 ToList,它在进行 ToLower 比较之前请求一个批处理 SQL,导致对服务器的请求只有一个

替代假设
如果分析器仅显示一个服务器执行,请尝试使用 Top 1 子句执行相同的查询,看看它是否需要很长时间。根据这篇文章(Why is doing a top(1) on an indexed column in SQL Server slow?),TOP 子句有时会干扰 SQL Server 优化器并使用正确的索引停止它。

好奇心编辑
尝试将 LINQ 更改为

var query = from x in Context.Users
            where x.Username.Equals(User.Identity.Name, StringComparison.OrdinalIgnoreCase)
            select x;

感谢 @Scott 找到在 LINQ 中进行不区分大小写比较的方法。试一试,看看它是否更快。

于 2013-08-20T14:27:53.083 回答
3

SQL 不会与 Linq 是延迟加载相同。因此,您的调用.ToList()将强制 .Net 评估表达式,然后在内存中选择该first()项目。

另一个选项应该添加top 1到 SQL 中的位置

例如

var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

 //SQL executed here
 var User = query.First();

var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

 //SQL executed here!
 var list = query.ToList();
 var User = query.First();

如下,第一个查询应该更快!我建议做一个SQL 分析器来看看发生了什么。查询的速度将取决于您的数据结构、记录数、索引等。

您的测试时间也会改变结果。正如一些人在评论中提到的那样,第一次点击 EF 时,它需要初始化并加载元数据。所以如果你一起运行这些,第一个应该总是很慢。

以下是有关 EF性能注意事项的更多信息

注意这一行:

实体框架使用的模型和映射元数据被加载到 MetadataWorkspace。此元数据被全局缓存,并且可用于同一应用程序域中的其他 ObjectContext 实例。

&

由于与数据库的打开连接会消耗宝贵的资源,因此实体框架仅在需要时打开和关闭数据库连接。您也可以显式打开连接。有关更多信息,请参阅在实体框架中管理连接和事务。

于 2013-08-20T14:07:37.713 回答
0

因此,优化器选择了一种不好的方式来运行查询。

由于您无法向 SQL 添加优化器提示以强制优化器选择更好的计划,因此我看到了两个选项。

  1. 在 select 中检索/包含的所有列上添加覆盖索引/索引视图非常可笑,但我认为它会起作用,因为该索引将使优化器轻松选择更好的计划。

  2. 总是过早地实现包括 First 或 Last 或 Take 的查询。危险,因为随着数据变大,在本地提取所有数据和执行 First() 以及在服务器上使用 Top 进行查询之间的收支平衡点将会改变。

http://geekswithblogs.net/Martinez/archive/2013/01/30/why-sql-top-may-slow-down-your-query-and-how.aspx

https://groups.google.com/forum/m/#!topic/microsoft.public.sqlserver.server/L2USxkyV1uw

http://connect.microsoft.com/SQLServer/feedback/details/781990/top-1-is-not-considered-as-a-factor-for-query-optimization

TOP 减慢查询速度

为什么 TOP 或 SET ROWCOUNT 使我的查询如此缓慢?

于 2013-08-21T15:27:24.863 回答