Tại sao LINQ JOIN nhanh hơn nhiều so với liên kết với WHERE?


99

Gần đây tôi đã nâng cấp lên VS 2010 và đang chơi với LINQ cho Dataset. Tôi có một tập dữ liệu được đánh máy mạnh cho Ủy quyền nằm trong HttpCache của Ứng dụng web ASP.NET.

Vì vậy, tôi muốn biết đâu là cách nhanh nhất để kiểm tra xem người dùng có được phép làm điều gì đó hay không. Đây là mô hình dữ liệu của tôi và một số thông tin khác nếu ai đó quan tâm.

Tôi đã kiểm tra 3 cách:

  1. cơ sở dữ liệu trực tiếp
  2. Truy vấn LINQ với điều kiện Where là "Tham gia" - Cú pháp
  3. Truy vấn LINQ với Tham gia - Cú pháp

Đây là kết quả với 1000 lệnh gọi trên mỗi hàm:

1. kiểm tra:

  1. 4,2841519 giây.
  2. 115,7796925 giây.
  3. 2,024749 giây.

2. đánh giá:

  1. 3.1954857 giây
  2. 84,97047 giây.
  3. 1,5783397 giây.

3. tùy chọn:

  1. 2.7922143 giây.
  2. 97,8713267 giây.
  3. 1,8432163 giây.

Trung bình cộng:

  1. Cơ sở dữ liệu: 3,4239506333 giây.
  2. Trong đó: 99,5404964 giây.
  3. Tham gia: 1,815435 giây.

Tại sao phiên bản Tham gia nhanh hơn rất nhiều so với cú pháp where khiến nó trở nên vô dụng mặc dù là một người mới LINQ, nó có vẻ là dễ đọc nhất. Hay tôi đã bỏ lỡ điều gì đó trong truy vấn của mình?

Đây là các truy vấn LINQ, tôi bỏ qua cơ sở dữ liệu:

Ở đâu :

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

Tham gia:

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

Cảm ơn bạn trước.


Chỉnh sửa : sau một số cải tiến trên cả hai truy vấn để nhận được các giá trị hiệu suất có ý nghĩa hơn, lợi thế của JOIN thậm chí còn lớn hơn nhiều lần so với trước đây:

Tham gia :

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

Ở đâu :

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

Kết quả cho 1000 cuộc gọi (trên máy tính nhanh hơn)

  1. Tham gia | 2. Ở đâu

1. kiểm tra:

  1. 0,0713669 giây.
  2. 12,7395299 giây.

2. đánh giá:

  1. 0,0492458 giây.
  2. 12,3885925 giây.

3. tùy chọn:

  1. 0,0501982 giây.
  2. 13,3474216 giây.

Trung bình cộng:

  1. Tham gia: 0,0569367 giây.
  2. Trong đó: 12,8251813 giây.

Tham gia nhanh hơn 225 lần

Kết luận: tránh WHERE để chỉ định quan hệ và sử dụng JOIN bất cứ khi nào có thể (chắc chắn trong LINQ đến DataSetLinq-To-Objectsnói chung).


Đối với những người khác đã đọc bài viết này và đang sử dụng LinqToSQL và nghĩ rằng có thể tốt khi thay đổi tất cả WHERE của bạn thành JOIN, vui lòng đảm bảo rằng bạn đã đọc nhận xét của THomas Levesque, nơi anh ấy nói "có một sự tối ưu hóa như vậy khi bạn sử dụng Linq to SQL hoặc Linq to Entities, vì truy vấn SQL đã tạo được DBMS coi như một phép nối. Nhưng trong trường hợp đó bạn đang sử dụng Linq to DataSet, không có bản dịch sang SQL ". Nói cách khác, đừng bận tâm thay đổi bất cứ điều gì khi bạn đang sử dụng linqtosql làm bản dịch của WHERE để kết hợp.
JonH

@JonH: sử dụng cũng không hại Joingì, tại sao phải dựa vào trình tối ưu hóa nếu bạn có thể viết mã tối ưu hóa ngay từ đầu? Nó cũng làm cho ý định của bạn rõ ràng hơn. Vì vậy, những lý do tương tự tại sao bạn nên thích THAM GIA trong sql .
Tim Schmelter,

Tôi có đúng khi cho rằng điều này sẽ không xảy ra với EntityFramework không?
Mafii

Câu trả lời:


76
  1. Cách tiếp cận đầu tiên của bạn (truy vấn SQL trong DB) khá hiệu quả vì DB biết cách thực hiện một phép nối. Nhưng không thực sự có ý nghĩa nếu so sánh nó với các cách tiếp cận khác, vì chúng hoạt động trực tiếp trong bộ nhớ (Linq to DataSet)

  2. Truy vấn với nhiều bảng và một Wheređiều kiện thực sự thực hiện tích lũy thừa của tất cả các bảng, sau đó lọc các hàng thỏa mãn điều kiện. Điều này có nghĩa là Wheređiều kiện được đánh giá cho mỗi tổ hợp hàng (n1 * n2 * n3 * n4)

  3. Các Joinnhà điều hành có các hàng từ bảng đầu tiên, sau đó chỉ mất các hàng với một chìa khóa phù hợp từ bảng thứ hai, sau đó chỉ có các hàng với một chìa khóa phù hợp từ bảng thứ ba, và vân vân. Điều này hiệu quả hơn nhiều, vì nó không cần thực hiện nhiều hoạt động


4
Cảm ơn bạn đã làm rõ lý lịch. Cách tiếp cận db không thực sự là một phần của câu hỏi này, nhưng tôi rất thú vị khi xem liệu cách tiếp cận bộ nhớ có thực sự nhanh hơn không. Tôi đã giả định rằng .net sẽ tối ưu hóa where-query theo một cách nào đó giống như một dbms. Trên thực tế, JOINthậm chí còn nhanh hơn 225 lần so với WHERE(lần chỉnh sửa cuối cùng).
Tim Schmelter

19

Các Joinnhanh hơn nhiều, bởi vì phương pháp này biết làm thế nào để kết hợp các bảng để giảm kết quả cho các kết hợp có liên quan. Khi bạn sử dụng Wheređể chỉ định mối quan hệ, nó phải tạo mọi kết hợp có thể có, sau đó kiểm tra điều kiện để xem những kết hợp nào có liên quan.

Các Joinphương pháp có thể thiết lập một bảng băm để sử dụng như một chỉ số để quicky nén hai bảng với nhau, trong khi Wherephương pháp chạy sau khi tất cả các kết hợp đã được tạo ra, vì vậy nó không thể sử dụng bất kỳ thủ thuật để làm giảm kết hợp trước đó.


Cảm ơn bạn. Không có tối ưu hóa ngầm nào từ trình biên dịch / thời gian chạy như trong dbms? Không thể không thấy rằng quan hệ where thực sự là một phép nối.
Tim Schmelter

1
Một RDBMS tốt thực sự phải phát hiện ra rằng điều kiện WHERE là một bài kiểm tra tính bình đẳng trên hai cột UNIQUE và coi nó như một JOIN.
Simon Richter

6
@Tim Schelter, có một sự tối ưu hóa như vậy khi bạn sử dụng Linq to SQL hoặc Linq to Entities, vì truy vấn SQL đã tạo được DBMS coi như một phép nối. Nhưng trong trường hợp bạn đang sử dụng LINQ to DataSet, không có dịch để SQL
Thomas Levesque

@Tim: LINQ tới DataSets thực sự sử dụng LINQ cho đối tượng. Do đó, các phép nối thực chỉ có thể được ghi lại bằng jointừ khóa, vì không có phân tích thời gian chạy của truy vấn để tạo ra bất kỳ thứ gì tương tự như một kế hoạch thực thi. Bạn cũng sẽ nhận thấy rằng các phép nối dựa trên LINQ chỉ có thể chứa các liên kết cột đơn.
Adam Robinson

2
@ Adam, đó không hẳn là đúng: bạn có thể làm equijoins với nhiều phím, sử dụng các loại vô danh:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque

7

những gì bạn thực sự cần biết là sql được tạo cho hai câu lệnh. Có một số cách để truy cập nhưng đơn giản nhất là sử dụng LinqPad. Có một số nút ngay phía trên kết quả truy vấn sẽ thay đổi thành sql. Điều đó sẽ cung cấp cho bạn rất nhiều thông tin hơn bất cứ điều gì khác.

Mặc dù vậy, thông tin tuyệt vời mà bạn đã chia sẻ ở đó.


1
Cảm ơn vì gợi ý LinqPad. Trên thực tế, hai truy vấn của tôi là linQ đến Dataset trong truy vấn bộ nhớ, do đó tôi giả định rằng không có SQL nào được tạo. Thông thường nó sẽ được tối ưu hóa bởi dbms.
Tim Schmelter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.