30.3.26

Ваш план исполнения лжёт Вам...и Вы об этом не догадываетесь

Автор: Luca Biondi, 😈 Your Execution Plan Is Lying to You ...And You Don’t Know It

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

Исполнение плана выглядит идеально…
но ваш запрос всё ещё медленно работает.

👉 Что-то лжёт вам.

И нет… не 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);

🎯 Финальная мысль

Планы выполнения не лгут.

Просто вы не знаете, где смотреть.

👉 Прекратите слепо доверять плану. Начните читать реальность.

Комментариев нет:

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