为什么添加一个不必要的ToList()会大大加快这个LINQ查询的速度?

时间:2013-08-20 14:04:04

标签: c# sql performance linq sql-server-2012

为什么使用ToList()强制实现会使我的查询数量级更快,如果有的话,它应该完全相反?

1)立即致电First()

    // "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的特性有关的所有事情都无关。

3 个答案:

答案 0 :(得分:11)

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

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

SELECT *
FROM Users

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

<强>假设
第一个查询是延迟加载结果集中的 EVERY 元素。然后进行.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服务器优化器并使用正确的索引来阻止它。

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

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

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

答案 1 :(得分:3)

SQL与Linq lazy loading不同。因此,您对.ToList()的调用将强制.Net评估表达式,然后在内存中选择first()项。

其他选项应该将top 1添加到SQL

E.G。

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 profiler来看看发生了什么。查询的速度取决于您的数据结构,记录数,索引等。

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

以下是有关EF performance considerations

的更多信息

注意这一行:

  

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

&安培;

  

因为与数据库的开放连接消耗了宝贵的价值   资源,实体框架打开和关闭数据库   仅在需要时连接。你也可以明确地打开   连接。有关更多信息,请参阅管理连接和   实体框架中的交易。

答案 2 :(得分:0)

因此,优化器选择了一种运行查询的错误方法。

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

  1. 在检索/包含在选择中的所有列上添加覆盖索引/索引视图相当荒谬,但我认为它会起作用,因为该索引会让它变得容易为优化者选择更好的计划。

  2. 始终过早地实现包含First或Last或Take的查询。危险,因为随着数据变大,在本地拉取所有数据和执行First()并在服务器上使用Top进行查询之间的收支平衡点将会发生变化。

  3. 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 slows down query

    Why does TOP or SET ROWCOUNT make my query so slow?