Как оптимизировать медленный отчет в Entity Framework при наличии индексов
Что проверяют
Умеете ли вы искать причину, а не перебирать советы. Фраза «индексы есть» подсказывает, что проблема, скорее всего, в том, какой SQL генерирует EF и как сервер его выполняет. Следом просят показать, как вы увидите реальный запрос и его план.
Как отвечать
Сначала диагностика, потом правки:
- увидеть SQL:
ToQueryString()на запросе или логирование черезLogTo. Важно, сколько запросов ушло: десятки одинаковых означают N+1 от ленивой загрузки; - посмотреть план: фактический план в SSMS, Query Store для истории, Extended Events вместо устаревшего Profiler. Ищите Scan вместо Seek, Key Lookup на тысячах строк, расхождение оценки строк с фактом;
- почему индекс не работает: функция над колонкой в
Where, поиск по подстроке черезContains, неявное преобразование типа. Классика: в базе колонкаvarchar, а в модели обычная строка, параметр уходит какnvarchar, и Seek превращается в Scan. ЛечитсяIsUnicode(false)илиHasColumnTypeв конфигурации; - меньше данных: проекция через
Selectв DTO,AsNoTracking(), агрегация на сервере черезGroupByиSum, а не в памяти послеToList(); - связи: несколько
Includeпо коллекциям дают декартов взрыв строк, помогаетAsSplitQuery(); - покрывающий индекс: индекс по фильтру с
INCLUDEнужных колонок убирает Key Lookup; - когда LINQ мешает: тяжёлый отчёт можно написать на SQL через
FromSqlилиDatabase.SqlQuery<T>, либо вынести расчёт в индексированное представление или заранее посчитанную таблицу.
Отдельно проверьте parameter sniffing: план, собранный под редкое значение параметра, потом медленно работает для остальных.
Где спотыкаются
Советуют async/await как ускорение. Асинхронность освобождает поток, но сам запрос быстрее не становится.
Добавляют Include на всё подряд «для производительности» и получают выборку в разы больше нужной.
Кэшируют медленный отчёт, не найдя причину. Первый запрос и отчёт по свежим данным остаются медленными, а кэш добавляет проблему устаревших цифр.