уверен, что это случится с вами в какой-то момент... и с множеством ваших коллег также. Клиент сообщает вам, что ваше приложение работает крайне медленно. Вы уже идентифицировали проблемный запрос и…
Исполнение плана выглядит идеально…
но ваш запрос всё ещё медленно работает.
👉 Что-то лжёт вам.
И нет… не SQL Server! ....это то, как вы читаете план выполнения.
⚠️ Ключевая проблема: Оценка против Фактических строк
Большинство разработчиков смотрят на планы выполнения и думают:
- Index Seek → хорошо
- Low cost → хорошо
- No warnings → идеально
- 👉 «Выглядит идеально… отправляйте»
Но реальная проблема здесь:
Estimated Rows ≠ Actual Rows
🧪 Пример 1 – Классическая ловушка
SELECT *
FROM Sales
WHERE CustomerId = @CustomerId;
План выполнения говорит:
- Оценка строк: 1
- Фактические строки: 250,000
💥 Результат:
- Выбор Nested Loop вместо Hash Join
- Повторяющиеся обращения к данным
- Взрывное увеличение нагрузки на CPU
- Запрос становится медленным
📊 Бенчмарк 1 – Хорошая против плохой оценки
| Сценарий | Оценка строк | Фактические строки | Логические чтения | CPU | Длительность |
|---|---|---|---|---|---|
| Хорошая оценка | 1000 | 1200 | 5,000 | 50 ms | 80 ms |
| Плохая оценка | 1 | 250,000 | 1,200,000 | 3,500 ms | 8,200 ms |
👉 Тот же самый запрос. Тот же самый индекс. Полностью разное поведение.
🧠 Почему так происходит (Кардинальность)
SQL Server использует статистику, чтобы оценить, сколько строк вернёт запрос.
Но оценка может оказаться неверной из-за:
- Устаревшая статистика
- Несбалансированное распределение данных
- Sniffing параметров
- Сложные предикаты
👉 SQL Server угадывает… и иногда угадывает ОЧЕНЬ неверно.
🧪 Пример 2 – Sniffing параметров
CREATE PROCEDURE GetOrders
@CustomerId INT
AS
SELECT *
FROM Orders
WHERE CustomerId = @CustomerId;
Первый запуск:
- @CustomerId = 1 → 1 строка
Второй запуск:
- @CustomerId = 500 → 300,000 строк
💥 Один и тот же план повторно используется → катастрофа.
📊 Бенчмарк 2 – Влияние Sniffing параметров
| Исполнение | Строки | Тип плана | Длительность |
|---|---|---|---|
| Первый (малый) | 1 | Index Seek + Nested Loop | 5 ms |
| Второй (большой) | 300,000 | 👉 Same plan reused | 6,500 ms |
👻 Скрытые проблемы, которые вы не видите
Планы выполнения не всегда явно показывают узкие места:
- Проблемы TempDB
- Проблемы с распределением памяти
- Implicit-преобразования
- Чрезмерное распараллеливание
💡 План выглядит чистым… время выполнения говорит другое.
🔍 Как распознать ложь
SET STATISTICS IO ON; SET STATISTICS TIME ON;-- Ваш запрос здесь
И всегда проверяйте:
- Фактические vs Оценочные строки
- Логические чтения
- Время CPU
- Статистика ожидания
🚀 Как исправить
- Обновить статистику
- Использовать OPTION (RECOMPILE)
- Оптимизировать индексы
- Переписать предикаты
- Использовать подходящие типы данных
Пример:
SELECT *
FROM Orders
WHERE CustomerId = @CustomerId
OPTION (RECOMPILE);
🎯 Финальная мысль
Планы выполнения не лгут.
Просто вы не знаете, где смотреть.
👉 Прекратите слепо доверять плану. Начните читать реальность.

Комментариев нет:
Отправить комментарий