Как решать проблему падения производительности запроса к базе данных после оптимизации и создания индексов
Что проверяют
Умеете ли вы искать причину по данным, а не добавлять индексы наугад. Ситуация типичная: индекс создали, запрос какое-то время летал, потом снова стал медленным или замедлились соседние операции. Следом обычно просят показать, как вы читаете план выполнения, и спрашивают про parameter sniffing.
Как отвечать
Начните с диагностики. Для SQL Server это фактический план выполнения, SET STATISTICS IO, TIME ON, Query Store с отчётом о регрессировавших запросах, Extended Events вместо устаревшего Profiler. Со стороны .NET: логирование SQL в EF Core через LogTo или ToQueryString(), чтобы увидеть, какой запрос реально уходит в базу.
Частые причины:
- parameter sniffing: план закешировался под нетипичное значение параметра и плох для остальных. Помогают
OPTION (RECOMPILE),OPTIMIZE FORили фиксация плана в Query Store; - устаревшая статистика: после массовой загрузки оптимизатор неверно оценивает число строк, выручает
UPDATE STATISTICS; - неявное преобразование типов: EF Core по умолчанию передаёт строки как
nvarchar, колонка при этомvarchar, и поиск по индексу превращается в сканирование. ЛечитсяIsUnicode(false)в конфигурации модели; - Key Lookup: индекс находит строки, но за остальными колонками база ходит в таблицу. Решается покрывающим индексом с
INCLUDE; - цена записи: каждый индекс замедляет
INSERTиUPDATE, и тормозить начинает уже другой запрос; - блокировки: запрос быстрый, но ждёт чужую транзакцию. Это видно по ожиданиям, а не по плану.
Проверьте и код: N+1 из-за ленивой загрузки, чтение без AsNoTracking(), выборка всех колонок вместо проекции через Select.
Где спотыкаются
Сразу предлагают ещё один индекс или кеш, не открыв план. Без плана это гадание.
Не могут объяснить, почему запрос в SSMS выполняется мгновенно, а из приложения медленно. Частая причина в разных настройках сессии, например ARITHABORT: для них кешируются разные планы.
Повторяют правило «JOIN всегда быстрее подзапроса». Современный оптимизатор часто строит для IN и EXISTS тот же план, решает замер.