LINQ JOIN neden WHERE ile bağlantı kurmaktan çok daha hızlı?


99

Yakın zamanda VS 2010'a yükselttim ve LINQ to Dataset ile uğraşıyorum. Bir ASP.NET WebApplication'ın HttpCache içinde olan Yetkilendirme için güçlü bir yazılmış veri kümesine sahibim.

Bu yüzden, bir kullanıcının bir şeyi yapmaya yetkili olup olmadığını kontrol etmenin en hızlı yolunun ne olduğunu bilmek istedim. İşte benim veri modelim ve ilgilenen biri varsa diğer bazı bilgiler.

3 yolu kontrol ettim:

  1. doğrudan veritabanı
  2. İle LINQ sorgusu nerede koşullar olarak "Katıl" - Sözdizimi
  3. Join ile LINQ sorgusu - Sözdizimi

Bunlar, her işlevde 1000 çağrı içeren sonuçlardır:

1. Yineleme:

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

2. Yineleme:

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

3. Yineleme:

  1. 2,7922143 sn.
  2. 97,8713267 sn.
  3. 1,8432163 sn.

Ortalama:

  1. Veritabanı: 3,4239506333 sn.
  2. Nerede: 99,5404964 sn.
  3. Birleştirme: 1,815435 sn.

Bir LINQ acemi olarak en okunaklı gibi görünse de neden Join sürümü onu kullanışsız kılan where-syntax'tan çok daha hızlı. Veya sorgularımda bir şeyi kaçırdım mı?

İşte LINQ sorguları, veritabanını atlıyorum:

Nerede :

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

Katılmak:

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

Şimdiden teşekkür ederim.


Düzenleme : Daha anlamlı performans değerleri elde etmek için her iki sorguda da bazı iyileştirmelerden sonra, JOIN'in avantajı öncekinden birçok kez daha büyüktür:

Katıl :

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

Nerede :

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 çağrı için sonuç (daha hızlı bir bilgisayarda)

  1. Katıl | 2. Nerede

1. Yineleme:

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

2. Yineleme:

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

3. Yineleme:

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

Ortalama:

  1. Birleştirme: 0,0569367 sn.
  2. Nerede: 12,8251813 sn.

Birleştirme 225 kat daha hızlıdır

Sonuç: WHERE'den ilişkileri belirtmekten kaçının ve mümkün olduğunda JOIN'i kullanın (kesinlikle LINQ to DataSet'te ve Linq-To-Objectsgenel olarak).


Bunu okuyan ve LinqToSQL kullanan ve tüm WHERE'lerinizi JOIN olarak değiştirmenin iyi olabileceğini düşünen diğer kişiler için, lütfen THomas Levesque'in "Linq to SQL kullandığınızda böyle bir optimizasyon var veya Linq to Entities, çünkü oluşturulan SQL sorgusu DBMS tarafından bir birleştirme olarak kabul edilir. Ancak bu durumda Linq to DataSet'i kullanıyorsunuz, SQL'e çeviri yoktur ". Başka bir deyişle, linqtosql'yi WHERE'nin birleşimlere çevrilmesi olarak kullanırken hiçbir şeyi değiştirmeye uğraşmayın.
JonH

@JonH: Kullanmanın zararı yok Join, eğer optimize edilmiş kodu baştan yazabiliyorsan neden bir optimize ediciye güveniyorsun? Ayrıca niyetlerinizi daha net hale getirir. Yani neden sql'de JOIN'i tercih etmeniz gerektiğiyle aynı nedenler .
Tim Schmelter

EntityFramework için durumun böyle olmayacağını varsaymakta doğru muyum?
Mafii

Yanıtlar:


76
  1. İlk yaklaşımınız (DB'deki SQL sorgusu) oldukça verimlidir çünkü DB bir birleştirmenin nasıl gerçekleştirileceğini bilir. Ancak, doğrudan bellekte çalıştıkları için diğer yaklaşımlarla karşılaştırmak gerçekten mantıklı değil (Linq'den DataSet'e)

  2. Birden çok tablo ve Wherekoşul içeren sorgu, aslında tüm tabloların kartezyen ürününü gerçekleştirir ve ardından koşulu karşılayan satırları filtreler. Bu, Wherekoşulun her satır kombinasyonu için değerlendirildiği anlamına gelir (n1 * n2 * n3 * n4)

  3. JoinOperatör daha sonra, üçüncü bir tablodan, bir eşleştirme anahtarı ile satırları, ve böylece ikinci tablodaki bir eşleştirme anahtarı, sadece satır alır, daha sonra birinci tablodan satır alır. Bu çok daha verimlidir, çünkü çok sayıda işlem gerçekleştirmesi gerekmez


4
Arka planı açıkladığınız için teşekkür ederiz. Db yaklaşımı aslında bu sorunun bir parçası değildi, ancak bellek yaklaşımının gerçekten daha hızlı olup olmadığını görmek benim için ilginçti. where.Net'in -query'yi bir dbms gibi bir şekilde optimize edeceğini varsaydım . Aslında (son düzenlemeden) JOIN225 kat daha hızlıydı WHERE.
Tim Schmelter

19

Bu Joinçok daha hızlıdır çünkü yöntem, sonucu ilgili kombinasyonlara indirgemek için tabloları nasıl birleştireceğini bilir. İlişkiyi Wherebelirtmek için kullandığınızda , olası her kombinasyonu yaratmalı ve ardından hangi kombinasyonların alakalı olduğunu görmek için koşulu test etmelidir.

JoinHızlıca iki tablo birlikte fermuarını ise yöntem, bir endeks olarak kullanmak için bir karma tablo ayarlayabilirsiniz Wheretüm kombinasyonların sonra yöntem çalışır zaten oluşturulur bunun önceden kombinasyonları azaltmak için herhangi bir hile kullanamaması için.


Teşekkür ederim. Derleyici / çalışma zamanından dbms'deki gibi örtük optimizasyonlar yok mu? Nerede-ilişkisinin aslında bir birleşim olduğunu görmek imkansız olmamalı.
Tim Schmelter

1
İyi bir RDBMS, WHERE koşulunun iki UNIQUE sütunda eşitlik testi olduğunu gerçekten fark etmeli ve bunu bir JOIN olarak ele almalıdır.
Simon Richter

6
@Tim Schelter, Linq to SQL veya Linq to Entities kullandığınızda böyle bir optimizasyon vardır, çünkü oluşturulan SQL sorgusu DBMS tarafından bir birleştirme olarak değerlendirilir. Ancak bu durumda Linq to DataSet'i kullanıyorsunuz, SQL'e çeviri yok
Thomas Levesque

@Tim: LINQ to DataSets aslında LINQ to Objects kullanır. Sonuç olarak, joinbir yürütme planına benzer bir şey üretmek için sorgunun çalışma zamanı analizi olmadığından , gerçek birleşimler yalnızca anahtar sözcükle yakalanabilir . Ayrıca LINQ tabanlı birleşimlerin yalnızca tek sütunlu ekijoinleri barındırabildiğini fark edeceksiniz.
Adam Robinson

2
@Adam, bu tam olarak doğru değil: anonim türleri kullanarak birden fazla anahtarla equijoins yapabilirsiniz:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque

7

gerçekten bilmeniz gereken şey, iki ifade için oluşturulan sql'dir. Buna ulaşmanın birkaç yolu var ancak en basit olanı LinqPad'i kullanmak. Sorgu sonuçlarının hemen üzerinde sql'ye dönüşecek birkaç düğme var. Bu size her şeyden çok daha fazla bilgi verecektir.

Yine de orada paylaştınız harika bilgiler.


1
LinqPad-ipucu için teşekkürler. Aslında benim iki sorgum bellek sorgularında linQ to Dataset şeklindedir, dolayısıyla üretilen SQL olmadığını varsayıyorum. Normalde dbms tarafından optimize edilir.
Tim Schmelter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.