Подтвердить что ты не робот

Почему добавление ненужного ToList() резко ускоряет этот запрос LINQ?

Почему принудительное использование материализации с помощью ToList() делает мои запросы на порядок быстрее, если что-либо, оно должно делать абсолютно противоположное?

1) Вызов First() немедленно

    // "Context" is an Entity Framework DB-first model

    var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

    var User = query.First();

    //  ** The above takes 30+ seconds to run **

2) Вызов First() после вызова ToList():

    var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

    var User = query.ToList().First();     // Added ToList() before First()

    // ** Now it takes < 1 second to run! **

Обновление и разрешение

После получения сгенерированного SQL единственная разница, как и ожидалось, добавляет TOP (1) в первый запрос. Как говорит Andyz Smith в своем ответе ниже, основной причиной является то, что оптимизатор SQL Server в этом конкретном случае выбирает худший план выполнения при добавлении TOP (1). Таким образом, проблема не имеет ничего общего с LINQ (что было правильно, добавив TOP (1)) и все, что связано с особенностями SQL Server.

4b9b3361

Ответ 1

Итак, оптимизатор выбирает плохой способ выполнить запрос.

Поскольку вы не можете добавить подсказки оптимизатора к SQL, чтобы заставить оптимизатор выбрать лучший план, я вижу два варианта.

  • Добавить индекс покрытия/индексный вид во всех столбцах, которые извлекаются/включаются в select. Довольно смехотворно, но я думаю, что это сработает, потому что этот индекс облегчит работу для оптимизатора выбрать лучший план.

  • Всегда преждевременно материализуйте запросы, которые включают First или Last или Take. Опасно, потому что, поскольку данные становятся больше, точка безубыточности между удалением всех данных локально и выполнением First() и выполнением запроса с Top на сервере будет изменяться.

http://geekswithblogs.net/Martinez/archive/2013/01/30/why-sql-top-may-slow-down-your-query-and-how.aspx

https://groups.google.com/forum/m/#!topic/microsoft.public.sqlserver.server/L2USxkyV1uw

http://connect.microsoft.com/SQLServer/feedback/details/781990/top-1-is-not-considered-as-a-factor-for-query-optimization

TOP замедляет запрос

Почему TOP или SET ROWCOUNT делают мой запрос настолько медленным?

Ответ 2

Я могу думать только об одной причине... Чтобы проверить его, можете ли вы удалить предложение Where и повторно запустить тест? Прокомментируйте здесь, если в результате первое выражение будет быстрее, и я объясню, почему.

Изменить
В предложение LINQ statement Where вы используете метод .ToLower() для строки. Я предполагаю, что LINQ не имеет встроенного преобразования в SQL для этого метода, поэтому результирующий SQL-это строка

SELECT *
FROM Users

Теперь мы знаем, что LINQ lazy load, но он также знает, что, поскольку он не оценил предложение Where, ему нужно загрузить элементы для сравнения.

Гипотезы
Первый запрос - это ленивая загрузка КАЖДЫЙ элемент в результирующем наборе. Затем он выполняет сравнение .ToLower() и возвращает первый результат. Это приводит к запросам n к серверу и огромным служебным нагрузкам. Не уверен, не видя SQL Tracelog.

Второй оператор вызывает ToList, который запрашивает пакетный SQL перед выполнением сравнения ToLower, в результате чего на сервер приходит только один запрос

Альтернативная гипотеза
Если профайлер показывает только одно выполнение сервера, попробуйте выполнить тот же запрос с предложением Top 1 и посмотреть, требуется ли оно так долго. В соответствии с этим сообщением (Почему верхняя часть (1) в индексированном столбце SQL Server медленна?) предложение TOP иногда может испортиться с оптимизатором SQL-сервера и остановить он использует правильные индексы.

Редактирование любопытства
попробуйте изменить LINQ на

var query = from x in Context.Users
            where x.Username.Equals(User.Identity.Name, StringComparison.OrdinalIgnoreCase)
            select x;

Кредит @Scott для поиска способа делать нечувствительное к регистру сравнение в LINQ. Дайте ему понять, быстрее ли это.

Ответ 3

SQL не будет таким же, как Linq ленивая загрузка. Таким образом, ваш вызов .ToList() заставит .NET оценивать выражение, затем в памяти выберите элемент first().

Где в качестве другой опции следует добавить top 1 в SQL

например.

var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

 //SQL executed here
 var User = query.First();

и

var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

 //SQL executed here!
 var list = query.ToList();
 var User = query.First();

Как ниже, первый запрос должен быть быстрее! Я бы предложил сделать SQL-профайлер, чтобы узнать, что происходит. Скорость запросов будет зависеть от вашей структуры данных, количества записей, индексов и т.д.

Время вашего теста также изменит результаты. Как отметили в комментарии несколько человек, в первый раз, когда вы попали в EF, ему нужно инициализировать и загружать метаданные. поэтому, если вы запускаете их вместе, первый должен всегда быть медленным.

Вот еще информация об EF соображения производительности

обратите внимание на строку:

Метаданные модели и отображения, используемые платформой Entity Framework, загружаются в метаданных. Эти метаданные кэшируются глобально и доступны к другим экземплярам ObjectContext в том же домене приложения.

&

Поскольку открытое соединение с базой данных потребляет ценную ресурс, Entity Framework открывает и закрывает базу данных соединение только по мере необходимости. Вы также можете явно открыть подключение. Дополнительные сведения см. В разделе Управление подключениями и Транзакции в платформе Entity Framework.