Чрезвычайно медленное и неэффективное выполнение запросов из Entity Framework

У меня есть Entity Framework 4.1 с .NET 4.5, работающий на ASP.NET в Windows 2008R2. Я использую EF-код для подключения к SQL Server 2008R2 и выполнения довольно сложного запроса LINQ, но в результате получается только Count().

Я воспроизвел проблему на двух разных веб-серверах, но только с одной базой данных (производство, конечно). Он недавно начал происходить без приложения, структуры базы данных или изменений сервера в Интернете или на стороне базы данных.

Моя проблема заключается в том, что выполнение запроса при определенных обстоятельствах занимает смехотворное количество времени (около 4 минут). Я могу взять фактический запрос, извлечь из SQL Profiler и выполнить в SSMS примерно через 1 секунду. Это непротиворечиво и воспроизводимо для меня, но если я изменил значение одного из параметров (параметр "Дата после 2015-01-22" ) на что-то раньше, например 2015-01-01 или позже, как 2015-02- 01, он отлично работает в EF. Но я вернул его к 2015-01-22 годам, и он снова замедлился. Я могу повторять это снова и снова.

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

Но это все говорит мне, что сами данные в порядке.

В Profiler, когда запрос выполняется правильно, он занимает около секунды или два, и показывает около 2 000 000 в чтениях и около 2000 в CPU. Когда он работает медленно, это занимает 3,5 минуты, а значения - 300 000 000 и 200 000 - поэтому чтение примерно в 150 раз выше, а процессор в 100 раз выше. Опять же, для идентичного оператора SQL.

Любые предложения по поводу того, что EF может делать по-другому, что не будет отображаться в тексте запроса? Есть ли какое-то скрытое свойство соединения, которое может привести к другому плану выполнения при определенных обстоятельствах?

ИЗМЕНИТЬ

Запрос, который создает EF, является одним из тех, где он строит гигантскую строку с параметром, включенным в текст, а не как параметр SQL:

exec sp_executesql 
   N'SELECT [GroupBy1].[A1] AS [C1] 
     FROM ( 
          SELECT COUNT(1) AS [A1]
           ...
           AND ([Extent1].[Added_Time] >= convert(datetime2, ''2015-01-22 00:00:00.0000000'', 121)) 
           ...
           ) AS [GroupBy1]'

ИЗМЕНИТЬ

Я не добавляю это как ответ, так как на самом деле он не затрагивает основную проблему, но в конечном итоге ее устраняют путем восстановления индексов и статистики перекомпоновки. Это было сделано не дольше, чем обычно, и, похоже, выяснилось, что вызвало проблему.

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

Спасибо за все идеи.

Ответ 1

Недавно у меня был очень похожий сценарий, запрос выполнялся очень быстро, выполняя его непосредственно в базе данных, но имел ужасную производительность с использованием EF (версия 5, в моем случае). Это не проблема сети, разница была от 4 мс до 10 минут.

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

Я не совсем уверен, почему это происходит, но из тестов, которые я сделал, это привело к тому, что база данных выполнила Сканирование индексов вместо Index Seek и, по-видимому, они очень разные по производительности.

comparison index scan and index seek

Я писал об этом здесь (отказ от ответственности: он находится на португальском языке), но позже я обнаружил, что Джимми Богард описал эту точную проблему в post с 2012 года, я предлагаю вам проверить его.

Поскольку у вас есть конвертер в вашем запросе, я бы сказал, начните оттуда. Дважды проверьте все сопоставления столбцов и проверьте различия между столбцом таблицы и свойством объекта. Избегайте неявных преобразований в вашем запросе. Если вы можете, проверьте план выполнения, чтобы найти какие-либо несоответствия, будьте в курсе желтого треугольника предупреждения, который может указывать на проблемы, подобные этому, о том, чтобы делать неявные преобразование:

query issueimplicit conversion warning

Надеюсь, это поможет вам как-то, это была действительно сложная проблема для нас, но в конце концов она имела смысл.

Ответ 2

Существует замечательная статья об оценке производительности Entity Framework здесь.

Я хотел бы обратить ваше внимание на раздел "Холодное" и "Теплый запрос":

В первый раз, когда выполняется какой-либо запрос по данной модели, Entity Framework выполняет много работы за кулисами для загрузки и проверить модель. Мы часто ссылаемся на этот первый запрос как на "холодный" запрос. Дополнительные запросы к уже загруженной модели известные как "теплые" запросы, и намного быстрее.

Во время выполнения запроса LINQ шаг "Загрузка метаданных" оказывает большое влияние на производительность для выполнения Cold запросов. Однако, когда загруженные метаданные будут кэшироваться, а будущие запросы будут выполняться намного быстрее. Метаданные кэшируются вне DbContext и будут повторно использоваться до тех пор, пока пул приложений будет работать.

Чтобы повысить производительность, рассмотрите следующие действия:

  • использовать предварительно сгенерированные представления
  • использовать кеш-план запроса
  • использовать запросы отслеживания (только при доступе только для чтения)
  • создать собственный образ Entity Framework (имеет значение только при использовании EF 6 или более поздней версии)

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

Ответ 3

У меня нет конкретного ответа на вопрос, почему это происходит, но, похоже, это связано с тем, как запрос обрабатывается больше, чем сам запрос. Если вы скажете, что у вас нет проблем с тем же сгенерированным запросом из SSMS, это не проблема.

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

Ответ 4

Просто чтобы выразить это, поскольку он не был рассмотрен как возможность:

Учитывая, что вы используете Entity Framework (EF), если вы используете Lazy Загрузка объектов, то EF требует, чтобы множество активных наборов результатов (MARS) включалось по строке подключения. Хотя это может показаться совершенно несвязанным, MARS иногда производит такое точное поведение чего-то быстро работающего в SSMS, но ужасно медленно (секунды становятся несколько минут) через EF.

Один из способов проверить это - отключить Lazy Loading и либо удалить MultipleActiveResultSets=True; (по умолчанию "false" ), либо, по крайней мере, изменить его на MultipleActiveResultSets=False;.

Насколько я знаю, к сожалению, к этому поведению не существует или исправлено (в настоящее время).

Вот пример этой проблемы: Тот же запрос с одним и тем же планом запроса занимает ~ 10 раз дольше, когда выполняется из ADO.NET и SMSS

Ответ 5

Понимая, что вы используете Entity Framework 4.1, я предлагаю вам перейти на Entity Framework 6.

Было много улучшений производительности, а EF 6 намного быстрее, чем EF 4.1.

статья MSDN об оценке производительности Entity Framework, упомянутая в моем другом ответе, также содержит сравнение между EF 4.1 и EF 6.

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