Чому LINQ ПРИЄДНАЄТЬСЯ набагато швидше, ніж зв'язок з ДЕЙ?


99

Нещодавно я перейшов до VS 2010 і граю разом з LINQ до Dataset. У мене є сильний типізований набір даних для авторизації, який знаходиться в HttpCache ASP.NET WebApplication.

Тож я хотів дізнатися, що насправді є найшвидшим способом перевірити, чи має право користувач щось робити. Ось моя модель даних та інша інформація, якщо хтось зацікавлений.

Я перевірив 3 способи:

  1. пряма база даних
  2. Запит LINQ з Де умови як "Приєднатися" - Синтаксис
  3. LINQ запит з реєстрації - Синтаксис

Це результати з 1000 дзвінків на кожну функцію:

1.Ітерація:

  1. 42841519 сек.
  2. 115,7796925 сек.
  3. 2024749 сек.

2.Ітерація:

  1. 31954857 с.
  2. 84 97047 сек.
  3. 1,5783397 сек.

3.Ітерація:

  1. 27922143 сек.
  2. 97 8713267 сек.
  3. 1,8432163 сек.

Середній:

  1. База даних: 3,4239506333 сек.
  2. Де: 99 5404964 сек.
  3. Приєднуйтесь: 1,815435 сек.

Чому Join-версія настільки швидша, ніж синтаксис where, який робить її марною, хоча як новачок 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

Спасибі заздалегідь.


Редагувати : після деяких вдосконалень обох запитів, щоб отримати більш значущі показники ефективності, перевага приєднання навіть у багато разів більша, ніж раніше:

Приєднуйтесь :

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

Результат для 1000 дзвінків (на більш швидкому комп’ютері)

  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 разів швидше

Висновок: уникайте, де БУДЬ вказувати відносини, і використовуйте ПРИЄДНАЙТЕ, коли це можливо (обов'язково в LINQ до DataSet і Linq-To-Objectsвзагалі).


Для інших, хто читає це і використовує LinqToSQL і думає, що, можливо, було б добре змінити всі ваші БУДИ на ПРИЄДНАЙТЕ, будь ласка, переконайтеся, що ви прочитали коментар Томаса Левеска, де він каже: "існує така оптимізація, коли ви використовуєте Linq для SQL або Linq to Entities, оскільки згенерований SQL-запит трактується як об'єднання СУБД. Але в цьому випадку ви використовуєте Linq до DataSet, перекладу в SQL немає ". Іншими словами, не турбуйтеся нічого змінювати, коли ви використовуєте linqtosql як переклад WHERE для приєднання.
JonH

@JonH: не завадить використовувати Joinanywhy, навіщо покладатися на оптимізатор, якщо ви можете написати оптимізований код з самого початку? Це також робить ваші наміри яснішими. Таким чином , по тимі ж причинам , чому ви повинні віддають перевагу JOIN в SQL .
Тім Шмелтер

Чи правильно я припускаю, що це не було б з EntityFramework?
Мафій

Відповіді:


76
  1. Ваш перший підхід (SQL-запит у БД) досить ефективний, оскільки БД знає, як виконати з'єднання. Але насправді не має сенсу порівнювати це з іншими підходами, оскільки вони працюють безпосередньо в пам'яті (Linq до DataSet)

  2. Запит з кількома таблицями та Whereумовою фактично виконує декартовий добуток усіх таблиць, а потім фільтрує рядки, які задовольняють умові. Це означає, що Whereумова оцінюється для кожної комбінації рядків (n1 * n2 * n3 * n4)

  3. JoinОператор бере рядок з перших таблиць, а потім приймає тільки ті рядки , за допомогою ключа узгодження з другої таблиці, то за виключенням окремих виразів з ключем узгоджувального з третьої таблиці, і так далі. Це набагато ефективніше, оскільки не потрібно виконувати стільки операцій


4
Дякую, що уточнили передумови. Підхід db насправді не був частиною цього питання, але мені було цікаво побачити, чи дійсно підхід до пам'яті швидше. Я припускав, що .net оптимізує whereзапит якось просто як dbms. Насправді це JOINбуло навіть у 225 разів швидше, ніж WHERE(остання редакція).
Тім Шмелтер

19

Це Joinнабагато швидше, оскільки метод знає, як поєднати таблиці, щоб зменшити результат до відповідних комбінацій. Коли ви використовуєте Whereдля вказівки відношення, він повинен створити всі можливі комбінації, а потім перевірити умову, щоб побачити, які комбінації є релевантними.

JoinМетод може створити хеш - таблицю для використання в якості індексу Quicky промайнути дві таблиці разом, в той час як Whereметод працює після всіх комбінацій вже створені, тому він не може використовувати будь-які хитрощі , щоб заздалегідь зменшити комбінації.


Дякую. Чи немає неявних оптимізацій від компілятора / часу виконання, як у dbms? Не повинно бути неможливим побачити, що відносини де насправді є з'єднанням.
Тім Шмелтер

1
Хороший RDBMS дійсно повинен помітити, що умова WHERE є тестом на рівність у двох УНІКАЛЬНИХ стовпцях, і трактувати це як ПРИЄДНАЙТЕСЬ.
Саймон Ріхтер

6
@Tim Schelter, існує така оптимізація, коли ви використовуєте Linq до SQL або Linq до Entities, оскільки згенерований SQL запит трактується як об'єднання СУБД. Але в тому випадку, коли ви використовуєте Linq для DataSet, немає перекладу на SQL
Thomas Levesque

@Tim: LINQ для наборів даних фактично використовує LINQ для об'єктів. Як результат, справжні об'єднання можуть бути зафіксовані лише за допомогою joinключового слова, оскільки не існує аналізу часу запиту для створення нічого аналогічного плану виконання. Ви також помітите, що приєднання на основі LINQ можуть вміщувати лише одноколонкові еквіхоїни.
Адам Робінсон

2
@ Адам, це не зовсім так: ви можете робити еджойні з декількома клавішами, використовуючи анонімні типи:... on new { f1.Key1, f1.Key2 } equals new { f2.Key1, f2.Key2 }
Thomas Levesque

7

що вам потрібно знати, це sql, який було створено для двох операторів. Є кілька способів дістатися до нього, але найпростішим є використання LinqPad. Над результатами запиту є кілька кнопок, які зміняться на sql. Це дасть вам набагато більше інформації, ніж будь-що інше.

Хоча чудова інформація, якою ви поділилися там.


1
Дякую за підказку LinqPad. Насправді два мої запити посилаються на набір даних у запитах пам'яті, отже, я припускаю, що SQL не генерується. Зазвичай це було б оптимізовано dbms.
Тім Шмелтер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.