为什么LINQ JOIN比链接到WHERE这么快?


99

我最近升级到了VS 2010,并且正在使用LINQ到Dataset。我在ASP.NET WebApplication的HttpCache中有一个用于授权的强类型数据集。

所以我想知道检查用户是否有权做某事的最快方法是什么。如果有人感兴趣,是我的数据模型和其他一些信息。

我检查了3种方法:

  1. 直接数据库
  2. LINQ查询,其中条件为“加入”-语法
  3. Join的 LINQ查询-语法

这些是每个函数进行1000次调用后的结果:

1.迭代

  1. 4,2841519秒
  2. 115,7796925秒
  3. 2,024749秒

2.迭代

  1. 3,1954857秒
  2. 84,97047秒
  3. 1,5783397秒

3,迭代

  1. 2,7922143秒
  2. 97,8713267秒。
  3. 1,8432163秒

平均:

  1. 数据库:3,4239506333秒
  2. 其中:99,5404964秒。
  3. 加入:1,815435秒。

为什么Join版本比where语法快得多,这使得它变得毫无用处,尽管作为LINQ新手,它似乎最清晰。还是我错过了查询中的内容?

这是LINQ查询,我跳过数据库:

哪里

Public Function hasAccessDS_Where(ByVal accessRule As String) As Boolean
    Dim userID As Guid = DirectCast(Membership.GetUser.ProviderUserKey, Guid)
    Dim query = From accRule In Authorization.dsAuth.aspnet_AccessRule, _
                roleAccRule In Authorization.dsAuth.aspnet_RoleAccessRule, _
                role In Authorization.dsAuth.aspnet_Roles, _
                userRole In Authorization.dsAuth.aspnet_UsersInRoles _
                Where accRule.idAccessRule = roleAccRule.fiAccessRule _
                And roleAccRule.fiRole = role.RoleId _
                And userRole.RoleId = role.RoleId _
                And userRole.UserId = userID And accRule.RuleName.Contains(accessRule)
                Select accRule.idAccessRule
    Return query.Any
End Function

加入:

Public Function hasAccessDS_Join(ByVal accessRule As String) As Boolean
    Dim userID As Guid = DirectCast(Membership.GetUser.ProviderUserKey, Guid)
    Dim query = From accRule In Authorization.dsAuth.aspnet_AccessRule _
                Join roleAccRule In Authorization.dsAuth.aspnet_RoleAccessRule _
                On accRule.idAccessRule Equals roleAccRule.fiAccessRule _
                Join role In Authorization.dsAuth.aspnet_Roles _
                On role.RoleId Equals roleAccRule.fiRole _
                Join userRole In Authorization.dsAuth.aspnet_UsersInRoles _
                On userRole.RoleId Equals role.RoleId _
                Where userRole.UserId = userID And accRule.RuleName.Contains(accessRule)
                Select accRule.idAccessRule
    Return query.Any
End Function

先感谢您。


编辑:在对两个查询进行一些改进以获取更有意义的性能值之后,JOIN的优势甚至比以前大很多倍:

加盟

Public Overloads Shared Function hasAccessDS_Join(ByVal userID As Guid, ByVal idAccessRule As Int32) As Boolean
    Dim query = From accRule In Authorization.dsAuth.aspnet_AccessRule _
                   Join roleAccRule In Authorization.dsAuth.aspnet_RoleAccessRule _
                   On accRule.idAccessRule Equals roleAccRule.fiAccessRule _
                   Join role In Authorization.dsAuth.aspnet_Roles _
                   On role.RoleId Equals roleAccRule.fiRole _
                   Join userRole In Authorization.dsAuth.aspnet_UsersInRoles _
                   On userRole.RoleId Equals role.RoleId _
                   Where accRule.idAccessRule = idAccessRule And userRole.UserId = userID
             Select role.RoleId
    Return query.Any
End Function

哪里

Public Overloads Shared Function hasAccessDS_Where(ByVal userID As Guid, ByVal idAccessRule As Int32) As Boolean
    Dim query = From accRule In Authorization.dsAuth.aspnet_AccessRule, _
           roleAccRule In Authorization.dsAuth.aspnet_RoleAccessRule, _
           role In Authorization.dsAuth.aspnet_Roles, _
           userRole In Authorization.dsAuth.aspnet_UsersInRoles _
           Where accRule.idAccessRule = roleAccRule.fiAccessRule _
           And roleAccRule.fiRole = role.RoleId _
           And userRole.RoleId = role.RoleId _
           And accRule.idAccessRule = idAccessRule And userRole.UserId = userID
           Select role.RoleId
    Return query.Any
End Function

1000次通话的结果(在更快的计算机上)

  1. 加入| 2.哪里

1.迭代

  1. 0,0713669秒
  2. 12,7395299秒

2.迭代

  1. 0,0492458秒
  2. 12,3885925秒

3,迭代

  1. 0,0501982秒
  2. 13,3474216秒

平均:

  1. 加入:0,0569367秒。
  2. 其中:12,8251813秒。

加入速度快225倍

结论:避免在哪里指定关系,并尽可能使用JOIN(在LINQ to DataSet中Linq-To-Objects一般情况下肯定是这样)。


对于其他阅读此书并使用LinqToSQL并认为将所有WHERE更改为JOIN的人来说可能会很好,请确保您阅读THomas Levesque的评论,他说:“当您使用Linq to SQL或Linq to Entities,因为DBMS将生成的SQL查询视为联接。但是,在这种情况下,您使用的是Linq to DataSet,则无法转换为SQL。换句话说,当您使用linqtosql时,不要费心更改任何内容,因为WHERE会转换为连接。
JonH 2014年

@JonH:使用它没有什么坏处Join,如果可以从头开始编写优化的代码,为什么要依赖优化器呢?这也使您的意图更加清晰。因此,出于同样的原因,您应该在sql中选择JOIN
蒂姆·施密特2015年

我是否可以正确地假设EntityFramework不是这种情况?
马菲

Answers:


76
  1. 您的第一种方法(数据库中的SQL查询)非常有效,因为数据库知道如何执行联接。但是,将其与其他方法进行比较并没有任何意义,因为它们直接在内存中工作(Linq to DataSet)

  2. 具有多个表和Where条件的查询实际上执行所有表的笛卡尔积然后过滤满足条件的行。这意味着将Where针对每个行组合(n1 * n2 * n3 * n4)评估条件

  3. Join操作者需要从所述第一表中的行,然后与从第二表中的匹配项,则仅利用从所述第三表中的匹配键的行中,等等只需要的行。这样效率更高,因为它不需要执行那么多操作


4
感谢您澄清背景。db方法实际上不是这个问题的一部分,但是让我感兴趣的是看看内存方法是否真的更快。我以为.net可以where像dbms一样以某种方式优化-query。实际上,该JOIN速度甚至比WHERE(上一次编辑)快225倍。
蒂姆·施密特

19

这样Join做要快得多,因为该方法知道如何组合表格以将结果减少为相关的组合。当您Where用来指定关系时,它必须创建每个可能的组合,然后测试条件以查看哪些组合是相关的。

Join方法可以设置一个哈希表以用作将两个表快速压缩在一起的索引,而该Where方法在所有组合都已创建之后运行,因此它不能使用任何技巧来预先减少组合。


谢谢。是否没有像dbms那样来自编译器/运行时的隐式优化?不可能不应该看到where关系实际上是一个联接。
蒂姆·施密特

1
好的RDBMS确实应该发现WHERE条件是对两个UNIQUE列是否相等的测试,并将其视为JOIN。
西蒙·里希特

6
@Tim Schelter,当您使用Linq to SQL或Linq to Entities时,会有这样的优化,因为DBMS将生成的SQL查询视为联接。但是在那种情况下,您使用的是Linq to DataSet,则无法转换为SQL
Thomas Levesque

@Tim:LINQ to DataSets实际上使用LINQ to Objects。结果,真正的联接只能用join关键字捕获,因为没有查询的运行时分析来产生类似于执行计划的任何内容。您还将注意到基于LINQ的联接只能容纳单列等联接。
亚当·罗宾逊

2
@Adam,并非完全正确:您可以使用匿名类型与多个键进行等值连接:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque

7

您真正需要知道的是为这两个语句创建的sql。有几种方法可以使用,但是最简单的方法是使用LinqPad。查询结果上方有几个按钮,这些按钮将更改为sql。这将为您提供更多的信息。

您在那分享的重要信息。


1
感谢LinqPad提示。实际上,我的两个查询是内存查询中的linQ to Dataset,因此我假设没有生成SQL。通常,它将由dbms优化。
蒂姆·施密特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.