Perché LINQ JOIN è molto più veloce del collegamento con WHERE?


99

Ho recentemente aggiornato a VS 2010 e sto giocando con LINQ to Dataset. Ho un set di dati tipizzato forte per l'autorizzazione che si trova in HttpCache di un'applicazione Web ASP.NET.

Quindi volevo sapere qual è effettivamente il modo più veloce per verificare se un utente è autorizzato a fare qualcosa. Ecco il mio modello di dati e alcune altre informazioni se qualcuno è interessato.

Ho verificato 3 modi:

  1. database diretto
  2. Query LINQ con condizioni Where come "Join" - Sintassi
  3. Query LINQ con Join - Sintassi

Questi sono i risultati con 1000 chiamate su ciascuna funzione:

1.Iterazione:

  1. 4.2841519 sec.
  2. 115,7796925 sec.
  3. 2.024749 sec.

2.Iterazione:

  1. 3.1954857 sec.
  2. 84,97047 sec.
  3. 1,5783397 sec.

3.Iterazione:

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

Media:

  1. Database: 3,4239506333 sec.
  2. Dove: 99,5404964 sec.
  3. Partecipa: 1.815435 sec.

Perché la versione Join è molto più veloce della sintassi where, il che la rende inutile anche se come principiante LINQ sembra essere la più leggibile. O mi sono perso qualcosa nelle mie domande?

Ecco le query LINQ, ignoro il database:

Dove :

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

Aderire:

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

Grazie in anticipo.


Modifica : dopo alcuni miglioramenti su entrambe le query per ottenere valori di prestazione più significativi, il vantaggio di JOIN è anche molte volte maggiore di prima:

Partecipa :

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

Dove :

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

Risultato per 1000 chiamate (su un computer più veloce)

  1. Partecipa | 2. Dove

1.Iterazione:

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

2.Iterazione:

  1. 0,0492458 sec.
  2. 12.3885925 sec.

3.Iterazione:

  1. 0,0501982 sec.
  2. 13.3474216 sec.

Media:

  1. Partecipa: 0,0569367 sec.
  2. Dove: 12,8251813 sec.

L'adesione è 225 volte più veloce

Conclusione: evitare WHERE per specificare le relazioni e utilizzare JOIN quando possibile (sicuramente in LINQ to DataSet e Linq-To-Objectsin generale).


Per gli altri che leggono questo e stanno usando LinqToSQL e pensano che potrebbe essere utile cambiare tutti i tuoi WHERE in JOINs, assicurati di leggere il commento di THomas Levesque dove dice "c'è una tale ottimizzazione quando usi Linq per SQL o Linq to Entities, perché la query SQL generata viene trattata come un join dal DBMS. Ma in quel caso stai usando Linq to DataSet, non c'è traduzione in SQL ". In altre parole, non preoccuparti di cambiare nulla quando usi linqtosql come WHERE si traduce in join.
JonH

@ JonH: non fa male usare Joinanywhy, perché affidarsi a un ottimizzatore se puoi scrivere il codice ottimizzato dall'inizio? Rende anche più chiare le tue intenzioni. Quindi gli stessi motivi per cui dovresti preferire JOIN in sql .
Tim Schmelter

Ho ragione a presumere che questo non sarebbe il caso di EntityFramework?
Mafii

Risposte:


76
  1. Il tuo primo approccio (query SQL nel DB) è abbastanza efficiente perché il DB sa come eseguire un join. Ma non ha davvero senso confrontarlo con gli altri approcci, poiché funzionano direttamente in memoria (da Linq a DataSet)

  2. La query con più tabelle e una Wherecondizione esegue effettivamente un prodotto cartesiano di tutte le tabelle, quindi filtra le righe che soddisfano la condizione. Ciò significa che la Wherecondizione viene valutata per ogni combinazione di righe (n1 * n2 * n3 * n4)

  3. L' Joinoperatore prende le righe dalla prima tabella, quindi prende solo le righe con una chiave corrispondente dalla seconda tabella, quindi solo le righe con una chiave corrispondente dalla terza tabella e così via. Questo è molto più efficiente, perché non ha bisogno di eseguire tante operazioni


4
Grazie per aver chiarito i precedenti. L'approccio db non faceva parte di questa domanda, ma è stato interessante per me vedere se l'approccio alla memoria è davvero più veloce. Ho pensato che .net avrebbe ottimizzato la wherequery in qualche modo proprio come un dbms. In realtà JOINera anche 225 volte più veloce della WHERE(ultima modifica).
Tim Schmelter

19

Il Joinè molto più veloce, perché il metodo sa coniugare le tabelle per ridurre il risultato alle combinazioni rilevanti. Quando si utilizza Whereper specificare la relazione, è necessario creare ogni possibile combinazione e quindi testare la condizione per vedere quali combinazioni sono rilevanti.

Il Joinmetodo può impostare una tabella hash da utilizzare come indice per comprimere rapidamente due tabelle insieme, mentre il Wheremetodo viene eseguito dopo che tutte le combinazioni sono già state create, quindi non può utilizzare alcun trucco per ridurre le combinazioni in anticipo.


Grazie. Non ci sono ottimizzazioni implicite dal compilatore / runtime come in dbms? Non dovrebbe essere impossibile vedere che la relazione dove è effettivamente un join.
Tim Schmelter

1
Un buon RDBMS dovrebbe infatti individuare che la condizione WHERE è un test di uguaglianza su due colonne UNIQUE e trattarlo come un JOIN.
Simon Richter

6
@Tim Schelter, esiste una tale ottimizzazione quando si utilizza Linq to SQL o Linq to Entities, perché la query SQL generata viene trattata come un join dal DBMS. Ma in quel caso stai usando Linq in DataSet, non c'è traduzione in SQL
Thomas Levesque

@Tim: LINQ to DataSets utilizza effettivamente LINQ to Objects. Di conseguenza, i true join possono essere acquisiti solo con la joinparola chiave, poiché non esiste un'analisi di runtime della query per produrre qualcosa di analogo a un piano di esecuzione. Noterai anche che i join basati su LINQ possono ospitare solo equijoin a colonna singola.
Adam Robinson

2
@Adam, non è esattamente vero: puoi fare equijoin con più chiavi, usando tipi anonimi:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque

7

quello che devi veramente sapere è lo sql che è stato creato per le due istruzioni. Ci sono alcuni modi per arrivarci, ma il più semplice è usare LinqPad. Ci sono diversi pulsanti proprio sopra i risultati della query che cambieranno in sql. Questo ti darà molte più informazioni di qualsiasi altra cosa.

Grandi informazioni che hai condiviso lì però.


1
Grazie per il suggerimento LinqPad. In realtà le mie due query sono linQ a Dataset nelle query di memoria, quindi presumo che non ci sia SQL generato. Normalmente sarebbe ottimizzato dal dbms.
Tim Schmelter
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.