なぜLINQ JOINはWHEREとリンクするよりもはるかに高速なのですか?


99

私は最近VS 2010にアップグレードし、LINQ to Datasetで遊んでいます。ASP.NET WebApplicationのHttpCacheにある承認用の厳密に型指定されたデータセットがあります。

だから私は、ユーザーが何かをすることを許可されているかどうかを確認する最も速い方法が実際に何であるか知りたいと思った。ここに私のデータモデルと、誰かが興味を持っている場合のその他の情報があります。

私は3つの方法をチェックしました:

  1. 直接データベース
  2. Where条件が "Join"のLINQクエリ-構文
  3. 結合を使用したLINQクエリ-構文

これらは、各関数を1000回呼び出した結果です。

1.反復:

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

2.Iteration:

  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秒

なぜLINQの初心者が最も読みやすいように見えますが、Joinバージョンはwhere構文よりもはるかに高速であり、役に立たなくなります。または、クエリで何かを見逃しましたか?

これが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.Iteration:

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

3.反復:

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

平均:

  1. 参加:0,0569367秒
  2. ここで:12,8251813秒。

参加は225倍速くなります

結論: WHEREを使用してリレーションを指定し、可能な場合は常にJOINを使用します(LINQ to DataSetおよびLinq-To-Objects一般的には間違いなく)。


これを読んでLinqToSQLを使用していて、すべてのWHEREをJOINに変更するのが良いと思う他の人は、THomas Levesqueのコメントを必ず読んでください。「Linq to SQLまたはLinq to Entities。生成されたSQLクエリはDBMSによって結合として扱われるためです。ただし、その場合はLinq to DataSetを使用しているため、SQLへの変換は行われません。言い換えると、WHEREが結合に変換されるときにlinqtosqlを使用しているときは、何も変更しないでください。
JonH 2014

@JonH:Joinなぜ使用しても害はありません。最初から最適化されたコードを記述できるのであれば、なぜオプティマイザに依存するのでしょうか。また、意図が明確になります。したがって、SQLでJOINを選択する必要があるのと同じ理由です
Tim Schmelter、2015

これはEntityFrameworkには当てはまらないと思いますか?
マフィイ2017

回答:


76
  1. 最初のアプローチ(DBでのSQLクエリ)は、DBが結合の実行方法を知っているため、非常に効率的です。しかし、それらはメモリ内で直接動作するため(Linq to DataSet)、他のアプローチと比較しても意味がありません。

  2. 複数のテーブルやクエリとのWhere条件は、実際に行ってデカルト積、すべてのテーブルのを、その後条件を満たす行をフィルタリングします。これは、Where条件が行の組み合わせごとに評価されることを意味します(n1 * n2 * n3 * n4)

  3. Joinオペレータは、次いで、第3のテーブルからの一致するキーを持つ行のみ、というように第二のテーブルからの一致するキーと行のみを取り、その後、最初のテーブルの行を取ります。多くの操作を実行する必要がないため、これははるかに効率的です。


4
背景を明確にしていただきありがとうございます。dbアプローチはこの質問の一部ではありませんでしたが、メモリアプローチが本当に速いかどうかを知るのは興味深いものでした。私は、.netがwheredbmsと同じように何らかの方法で-query を最適化すると想定しました。実際にJOINは、WHERE(最後の編集)よりも225倍も高速でした。
Tim Schmelter、2011

19

Joinこの方法は、関連する組み合わせに結果を減らすために、テーブルを結合する方法を知っているので、はるかに高速です。を使用Whereして関係を指定する場合、可能なすべての組み合わせを作成し、条件をテストして、関連する組み合わせを確認する必要があります。

このJoinメソッドは、2つのテーブルをすばやく圧縮するためのインデックスとして使用するハッシュテーブルを設定できますが、このWhereメソッドはすべての組み合わせがすでに作成された後に実行されるため、トリックを使用して組み合わせを事前に減らすことはできません。


ありがとうございました。dbmsのようなコンパイラ/ランタイムからの暗黙的な最適化はありませんか?where関係が実際に結合であることを確認することは不可能ではないはずです。
Tim Schmelter

1
優れたRDBMSは確かに、WHERE条件が2つのUNIQUE列の等価性のテストであることを特定し、それをJOINとして扱う必要があります。
Simon Richter

6
@Tim Schelterさん、Linq to SQLまたはLinq to Entitiesを使用すると、生成されたSQLクエリがDBMSによって結合として扱われるため、このような最適化が行われます。ただし、Linq to DataSetを使用している場合、SQLへの変換はありません
Thomas Levesque

@Tim:LINQ to DataSetsは実際にはLINQ to Objectを使用します。その結果、join実行プランに類似したものを生成するクエリの実行時分析がないため、真の結合はキーワードでのみキャプチャできます。また、LINQベースの結合は、単一列の等価結合にしか対応できないことにも気づくでしょう。
Adam Robinson

2
@Adam、それは正確ではありません:匿名型を使用して、複数のキーで等価結合を行うことができます:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque

7

実際に知っておく必要があるのは、2つのステートメント用に作成されたSQLです。いくつかの方法がありますが、最も簡単な方法はLinqPadを使用することです。sqlに変更されるクエリ結果の真上にいくつかのボタンがあります。それはあなたに何よりも多くの情報を提供します。

あなたがそこで共有した素晴らしい情報。


1
LinqPadヒントをありがとう。実際、私の2つのクエリはlinQ to Dataset in memoryクエリなので、SQLが生成されていないと想定しています。通常、dbmsによって最適化されます。
Tim Schmelter、2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.