Mengapa LINQ JOIN jauh lebih cepat daripada menghubungkan dengan WHERE?

99

Saya baru saja meningkatkan ke VS 2010 dan bermain-main dengan LINQ ke Dataset. Saya memiliki set data yang diketik kuat untuk Otorisasi yang ada di HttpCache dari ASP.NET WebApplication.

Jadi saya ingin tahu apa sebenarnya cara tercepat untuk memeriksa apakah pengguna berwenang untuk melakukan sesuatu. Berikut adalah model data saya dan beberapa informasi lain jika ada yang tertarik.

Saya telah memeriksa 3 cara:

  1. database langsung
  2. Query LINQ dengan kondisi Where sebagai "Join" - Syntax
  3. Kueri LINQ dengan Gabung - Sintaks

Ini adalah hasil dengan 1000 panggilan di setiap fungsi:

1. Iterasi:

  1. 4,2841519 dtk.
  2. 115,7796925 dtk.
  3. 2.024749 dtk.

2. Iterasi:

  1. 3,1954857 dtk.
  2. 84.97047 dtk.
  3. 1.5783397 dtk.

3. Iterasi:

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

Rata-rata:

  1. Database: 3.4239506333 dtk.
  2. Dimana: 99,5404964 dtk.
  3. Bergabung: 1,815435 dtk.

Mengapa versi Gabung jauh lebih cepat daripada sintaks di mana yang membuatnya tidak berguna meskipun sebagai pemula LINQ tampaknya yang paling terbaca. Atau apakah saya melewatkan sesuatu dalam pertanyaan saya?

Berikut adalah kueri LINQ, saya melewatkan database:

Dimana :

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

Ikuti:

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

Terima kasih sebelumnya.


Sunting : setelah beberapa perbaikan pada kedua kueri untuk mendapatkan nilai kinerja yang lebih bermakna, keuntungan dari JOIN bahkan jauh lebih besar dari sebelumnya:

Gabung :

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

Dimana :

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

Hasil untuk 1000 panggilan (di komputer yang lebih cepat)

  1. Gabung | 2. Dimana

1. Iterasi:

  1. 0,0713669 dtk.
  2. 12.7395299 dtk.

2. Iterasi:

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

3. Iterasi:

  1. 0,0501982 dtk.
  2. 13.3474216 dtk.

Rata-rata:

  1. Bergabung: 0,0569367 dtk.
  2. Dimana: 12,8251813 dtk.

Bergabung 225 kali lebih cepat

Kesimpulan: hindari WHERE untuk menentukan relasi dan gunakan JOIN bila memungkinkan (pasti di LINQ ke DataSet dan Linq-To-Objectssecara umum).

Tim Schmelter
sumber
Bagi orang lain yang membaca ini dan menggunakan LinqToSQL dan berpikir bahwa mungkin bagus untuk mengubah semua WHERE Anda menjadi GABUNG, pastikan Anda membaca komentar oleh THomas Levesque di mana dia mengatakan "ada optimasi seperti itu ketika Anda menggunakan Linq ke SQL atau Linq ke Entitas, karena kueri SQL yang dihasilkan diperlakukan sebagai gabungan oleh DBMS. Namun dalam kasus ini Anda menggunakan Lini ke DataSet, tidak ada terjemahan ke SQL ". Dengan kata lain, jangan repot-repot mengubah apa pun saat Anda menggunakan linqtosql sebagai WHERE menerjemahkan untuk bergabung.
Yunus
@ JonH: tidak ada salahnya menggunakan Joinanywhy, mengapa mengandalkan pengoptimal jika Anda dapat menulis kode opimasi dari awal? Itu juga membuat niat Anda lebih jelas. Jadi alasan yang sama mengapa Anda harus lebih memilih GABUNG di sql .
Tim Schmelter
Apakah saya benar untuk berasumsi bahwa tidak demikian halnya dengan EntityFramework?
Mafii

Jawaban:

76
  1. Pendekatan pertama Anda (kueri SQL di DB) cukup efisien karena DB tahu cara melakukan gabungan. Tetapi tidak masuk akal untuk membandingkannya dengan pendekatan lain, karena mereka bekerja secara langsung di memori (Linq ke DataSet)

  2. Kueri dengan beberapa tabel dan Wherekondisi benar-benar menjalankan produk kartesius dari semua tabel, lalu memfilter baris yang memenuhi ketentuan tersebut. Ini berarti Wherekondisi dievaluasi untuk setiap kombinasi baris (n1 * n2 * n3 * n4)

  3. The JoinOperator mengambil baris dari tabel pertama, kemudian mengambil hanya baris dengan kunci pencocokan dari tabel kedua, maka hanya baris dengan kunci yang cocok dari meja ketiga, dan seterusnya. Ini jauh lebih efisien, karena tidak perlu melakukan banyak operasi

Thomas Levesque
sumber
4
Terima kasih telah menjelaskan latar belakangnya. Pendekatan db sebenarnya bukan bagian dari pertanyaan ini, tetapi menarik bagi saya untuk melihat apakah pendekatan memori benar-benar lebih cepat. Saya berasumsi bahwa .net akan mengoptimalkan where-query dalam beberapa cara seperti dbms. Sebenarnya JOINitu bahkan 225 kali lebih cepat dari WHERE(edit terakhir).
Tim Schmelter
19

Ini Joinjauh lebih cepat, karena metode ini tahu bagaimana menggabungkan tabel untuk mengurangi hasil ke kombinasi yang relevan. Saat Anda menggunakan Whereuntuk menetapkan relasinya, ia harus membuat setiap kemungkinan kombinasi, dan kemudian menguji kondisi untuk melihat kombinasi mana yang relevan.

The JoinMetode dapat mengatur tabel hash untuk digunakan sebagai indeks untuk quicky zip dua tabel bersama-sama, sedangkan Wheremetode berjalan setelah semua kombinasi yang sudah dibuat, sehingga tidak dapat menggunakan trik apapun untuk mengurangi kombinasi terlebih dahulu.

Guffa
sumber
Terima kasih. Apakah tidak ada pengoptimalan implisit dari compiler / runtime seperti di dbms? Seharusnya tidak mustahil untuk melihat bahwa relasi where sebenarnya adalah gabungan.
Tim Schmelter
1
RDBMS yang baik memang harus melihat bahwa kondisi WHERE adalah pengujian kesetaraan pada dua kolom UNIQUE dan memperlakukannya sebagai GABUNG.
Simon Richter
6
@ Tim Schelter, ada optimasi seperti itu ketika Anda menggunakan Lini ke SQL atau Lini ke Entitas, karena kueri SQL yang dihasilkan diperlakukan sebagai gabungan oleh DBMS. Tetapi dalam kasus ini Anda menggunakan Linq ke DataSet, tidak ada terjemahan ke SQL
Thomas Levesque
@ Tim: LINQ ke Kumpulan Data sebenarnya menggunakan LINQ ke Objek. Akibatnya, gabungan yang sebenarnya hanya dapat ditangkap dengan joinkata kunci, karena tidak ada analisis waktu proses kueri untuk menghasilkan sesuatu yang serupa dengan rencana eksekusi. Anda juga akan melihat bahwa gabungan berbasis LINQ hanya dapat mengakomodasi equijoin kolom tunggal.
Adam Robinson
2
@Adam, itu tidak sepenuhnya benar: Anda dapat melakukan equijoin dengan beberapa kunci, menggunakan jenis anonim:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque
7

yang benar-benar perlu Anda ketahui adalah sql yang dibuat untuk dua pernyataan. Ada beberapa cara untuk mendapatkannya, tetapi yang paling sederhana adalah dengan menggunakan LinqPad. Ada beberapa tombol tepat di atas hasil query yang akan berubah menjadi sql. Itu akan memberi Anda lebih banyak informasi daripada apa pun.

Informasi hebat yang Anda bagikan di sana.

Phillip
sumber
1
Terima kasih atas petunjuk-LinqPad. Sebenarnya dua pertanyaan saya adalah linQ ke Dataset dalam permintaan memori, oleh karena itu saya berasumsi bahwa tidak ada SQL yang dihasilkan. Biasanya ini akan dioptimalkan oleh dbms.
Tim Schmelter