ทำไม LINQ JOIN จึงเร็วกว่าการเชื่อมโยงกับ WHERE มาก?


99

ฉันเพิ่งอัปเกรดเป็น VS 2010 และกำลังเล่นกับ LINQ เป็น Dataset ฉันมีชุดข้อมูลที่พิมพ์แน่นหนาสำหรับการอนุญาตที่อยู่ใน HttpCache ของ ASP.NET WebApplication

ฉันจึงอยากรู้ว่าอะไรคือวิธีที่เร็วที่สุดในการตรวจสอบว่าผู้ใช้ได้รับอนุญาตให้ทำบางสิ่งหรือไม่ นี่คือรูปแบบข้อมูลของฉันและข้อมูลอื่น ๆ หากมีใครสนใจ

ฉันตรวจสอบแล้ว 3 วิธี:

  1. ฐานข้อมูลโดยตรง
  2. แบบสอบถาม LINQ กับที่ไหนเงื่อนไขที่ "เข้าร่วม" - ไวยากรณ์
  3. แบบสอบถาม LINQ พร้อมเข้าร่วม - ไวยากรณ์

นี่คือผลลัพธ์ที่มีการโทร 1,000 ครั้งในแต่ละฟังก์ชัน:

1. การนึ่ง:

  1. 4,2841519 วินาที
  2. 115,7796925 วินาที
  3. 2,024749 วินาที

2. การนึ่ง:

  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 วินาที

เหตุใดเวอร์ชัน Join จึงเร็วกว่าไวยากรณ์ที่ซึ่งทำให้ไม่มีประโยชน์แม้ว่าในฐานะมือใหม่ LINQ ดูเหมือนว่าจะชัดเจนที่สุด หรือฉันพลาดอะไรบางอย่างในคำถามของฉัน?

นี่คือคำถาม 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

ผลลัพธ์สำหรับการโทร 1,000 ครั้ง (บนคอมพิวเตอร์ที่เร็วกว่า)

  1. เข้าร่วม | 2. ที่ไหน

1. การนึ่ง:

  1. 0,0713669 วินาที
  2. 12,7395299 วินาที

2. การนึ่ง:

  1. 0,0492458 วินาที
  2. 12,3885925 วินาที

3. การนึ่ง:

  1. 0,0501982 วินาที
  2. 13,3474216 วินาที

เฉลี่ย:

  1. เข้าร่วม: 0,0569367 วินาที
  2. ที่ไหน: 12,8251813 วินาที

เข้าร่วมเร็วขึ้น 225 เท่า

สรุป:หลีกเลี่ยง WHERE เพื่อระบุความสัมพันธ์และใช้ JOIN ทุกครั้งที่ทำได้ (แน่นอนในLINQ ไปยัง DataSetและLinq-To-Objectsโดยทั่วไป)


สำหรับคนอื่น ๆ ที่อ่านสิ่งนี้และกำลังใช้ LinqToSQL และคิดว่าการเปลี่ยน WHEREs ทั้งหมดของคุณให้เป็น JOIN นั้นเป็นการดีโปรดตรวจสอบให้แน่ใจว่าคุณได้อ่านความคิดเห็นของ THomas Levesque ซึ่งเขากล่าวว่า "มีการปรับให้เหมาะสมเช่นนี้เมื่อคุณใช้ Linq กับ SQL หรือ Linq เป็นเอนทิตีเนื่องจากการสืบค้น SQL ที่สร้างขึ้นจะถือว่าเป็นการรวมโดย DBMS แต่ในกรณีนี้คุณกำลังใช้ Linq เป็น DataSet จะไม่มีการแปลเป็น SQL " กล่าวอีกนัยหนึ่งไม่ต้องกังวลกับการเปลี่ยนแปลงอะไรเลยเมื่อคุณใช้ linqtosql เป็น WHERE แปลเพื่อเข้าร่วม
JonH

@ จอนห์: มันไม่เจ็บที่จะใช้Joinแต่ทำไมต้องพึ่งพาเครื่องมือเพิ่มประสิทธิภาพถ้าคุณสามารถเขียนโค้ด opimized ตั้งแต่เริ่มต้น? นอกจากนี้ยังทำให้ความตั้งใจของคุณชัดเจนขึ้น ดังนั้นด้วยเหตุผลเดียวกันว่าทำไมคุณควรต้องการเข้าร่วมใน SQL
Tim Schmelter

ฉันถูกต้องหรือไม่ที่จะคิดว่านี่จะไม่ใช่กรณีของ EntityFramework
Mafii

คำตอบ:


76
  1. แนวทางแรกของคุณ (แบบสอบถาม SQL ใน DB) ค่อนข้างมีประสิทธิภาพเนื่องจาก DB รู้วิธีดำเนินการเข้าร่วม แต่มันไม่สมเหตุสมผลเลยที่จะเปรียบเทียบกับแนวทางอื่น ๆ เนื่องจากพวกเขาทำงานในหน่วยความจำโดยตรง (Linq ไปยัง DataSet)

  2. แบบสอบถามที่มีตารางหลายตารางและWhereเงื่อนไขจะดำเนินการผลิตภัณฑ์คาร์ทีเซียนของตารางทั้งหมดจากนั้นกรองแถวที่ตรงตามเงื่อนไข ซึ่งหมายความว่าWhereเงื่อนไขจะถูกประเมินสำหรับการรวมกันของแต่ละแถว (n1 * n2 * n3 * n4)

  3. ตัวJoinดำเนินการจะนำแถวจากตารางแรกจากนั้นนำเฉพาะแถวที่มีคีย์ตรงกันจากตารางที่สองจากนั้นเลือกเฉพาะแถวที่มีคีย์ที่ตรงกันจากตารางที่สามเป็นต้น วิธีนี้มีประสิทธิภาพมากขึ้นเนื่องจากไม่จำเป็นต้องดำเนินการมากนัก


4
ขอบคุณสำหรับการชี้แจงความเป็นมา วิธี db ไม่ได้เป็นส่วนหนึ่งของคำถามนี้จริงๆ แต่เป็นเรื่องที่น่าสนใจสำหรับฉันที่จะดูว่าวิธีการจำเร็วขึ้น ฉันคิดว่า. net จะเพิ่มประสิทธิภาพwhere-query ไม่ทางใดก็ทางหนึ่งเช่นเดียวกับ dbms ที่จริงJOINเร็วกว่าWHERE(แก้ไขล่าสุด) ถึง 225 เท่า
Tim Schmelter

19

Joinเร็วเพราะวิธีการที่รู้วิธีที่จะรวมตารางเพื่อลดผลให้อยู่รวมกันที่เกี่ยวข้อง เมื่อคุณใช้Whereเพื่อระบุความสัมพันธ์จะต้องสร้างชุดค่าผสมที่เป็นไปได้ทั้งหมดจากนั้นทดสอบเงื่อนไขเพื่อดูว่าชุดค่าผสมใดเกี่ยวข้อง

Joinวิธีสามารถตั้งค่าตารางแฮชที่จะใช้เป็นดัชนีเพื่อ quicky ซิปสองตารางด้วยกันในขณะที่Whereวิธีการทำงานหลังจากที่รวมกันทั้งหมดจะถูกสร้างไว้แล้วดังนั้นจึงไม่สามารถใช้เทคนิคใด ๆ เพื่อลดการรวมกันก่อน


ขอบคุณ. ไม่มีการเพิ่มประสิทธิภาพโดยนัยจากคอมไพเลอร์ / รันไทม์เหมือนใน dbms หรือไม่? ไม่ควรเป็นไปไม่ได้ที่จะเห็นว่าความสัมพันธ์ที่แท้จริงคือการเข้าร่วม
Tim Schmelter

1
RDBMS ที่ดีควรระบุว่าเงื่อนไข WHERE เป็นการทดสอบความเท่าเทียมกันในคอลัมน์ UNIQUE สองคอลัมน์และถือว่าเป็น JOIN
Simon Richter

6
@Tim Schelter มีการเพิ่มประสิทธิภาพดังกล่าวเมื่อคุณใช้ Linq เป็น SQL หรือ Linq กับเอนทิตีเนื่องจากแบบสอบถาม SQL ที่สร้างขึ้นจะถือว่าเป็นการรวมโดย DBMS แต่ในกรณีนั้นคุณกำลังใช้ Linq เป็น DataSet ไม่มีการแปลเป็น SQL
Thomas Levesque

@Tim: LINQ to DataSets ใช้ LINQ กับ Objects เป็นผลให้การรวม true สามารถจับได้ด้วยjoinคีย์เวิร์ดเท่านั้นเนื่องจากไม่มีการวิเคราะห์รันไทม์ของแบบสอบถามเพื่อสร้างสิ่งใดที่คล้ายคลึงกับแผนการดำเนินการ นอกจากนี้คุณจะสังเกตเห็นว่าการรวมแบบ LINQ สามารถรองรับได้เฉพาะเส้นศูนย์สูตรคอลัมน์เดียว
Adam Robinson

2
@Adam นั่นไม่เป็นความจริงอย่างแน่นอน: คุณสามารถทำ equijoins ด้วยปุ่มหลายปุ่มโดยใช้ประเภทที่ไม่ระบุตัวตน:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque

7

สิ่งที่คุณต้องรู้จริงๆคือ sql ที่สร้างขึ้นสำหรับสองคำสั่ง มีสองสามวิธีในการเข้าถึง แต่วิธีที่ง่ายที่สุดคือการใช้ LinqPad มีปุ่มหลายปุ่มเหนือผลการค้นหาที่จะเปลี่ยนเป็น sql ที่จะให้ข้อมูลมากมายกว่าสิ่งอื่นใด

ข้อมูลดีๆที่คุณแบ่งปันที่นั่น


1
ขอบคุณสำหรับคำใบ้ LinqPad จริงๆแล้วคำค้นหาสองคำของฉันคือ linQ ไปยัง Dataset ในแบบสอบถามหน่วยความจำดังนั้นฉันจึงถือว่าไม่มี SQL ที่สร้างขึ้น โดยปกติจะได้รับการปรับให้เหมาะสมโดย dbms
Tim Schmelter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.