Почему LINQ JOIN намного быстрее, чем связывание с WHERE?

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

Итак, я хотел знать, что на самом деле является самым быстрым способом проверить, разрешено ли пользователю что-то делать. Здесь - это мой datamodel и некоторые другие сведения, если кому-то интересно.

Я проверил 3 способа:

  • прямая база данных
  • Запрос LINQ с условиями Где как "Присоединиться" - Синтаксис
  • Запрос LINQ с Присоединиться - Синтаксис

Это результаты с 1000 вызовами для каждой функции:

1.Iteration:

  • 4,2841519 сек.
  • 115,7796925 с.
  • 2,024749 сек.

2.Iteration:

  • 3,1954857 сек.
  • 84,97047 сек.
  • 1,5783397 сек.

3.Iteration:

  • 2,7922143 сек.
  • 97,8713267 сек.
  • 1,8432163 сек.

Средний:

  • База данных: 3,4239506333 сек.
  • Где: 99 5404964 сек.
  • Присоединиться: 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

Join:

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 даже во много раз больше, чем раньше:

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

Результат для 1000 вызовов (на более быстром компьютере)

  • Присоединиться | 2. Где

1.Iteration:

  • 0,0713669 сек.
  • 12,7395299 sec.

2.Iteration:

  • 0,0492458 с.
  • 12,3885925 сек.

3.Iteration:

  • 0,0501982 сек.
  • 13,3474216 с.

Средний:

  • Присоединиться: 0,0569367 сек.
  • Где: 12,8251813 сек.

Соединение в 225 раз быстрее

Вывод: избегать WHERE, чтобы указать отношения и использовать JOIN по возможности (определенно в LINQ to DataSet и Linq-To-Objects в целом).

Ответ 1

  • Ваш первый подход (SQL-запрос в БД) довольно эффективен, потому что БД знает, как выполнить соединение. Но на самом деле нет смысла сравнивать его с другими подходами, поскольку они работают непосредственно в памяти (Linq to DataSet).

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

  • Оператор Join берет строки из первых таблиц, затем берет только строки с соответствующим ключом из второй таблицы, а затем только строки с соответствующим ключом из третьей таблицы и т.д. Это намного эффективнее, потому что ему не нужно выполнять столько операций

Ответ 2

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

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

Ответ 3

то, что вам действительно нужно знать, это sql, который был создан для двух операторов. Есть несколько способов добраться до него, но проще всего использовать LinqPad. Есть несколько кнопок прямо над результатами запроса, которые будут изменены на sql. Это даст вам гораздо больше информации, чем что-либо еще.

Отличная информация, которую вы там поделили.