Что такое план выполнения запроса (execution plan) в SQL?

Ответ

План выполнения запроса (Query Execution Plan) — это последовательность шагов (операций), которую СУБД (например, SQL Server, PostgreSQL, MySQL) выбирает для извлечения или изменения данных, соответствующих SQL-запросу. Это «дорожная карта», показывающая, как движок базы данных будет выполнять ваш запрос.

Что показывает план?

  • Порядок доступа к таблицам: Какие таблицы читаются и в каком порядке (JOIN).
  • Методы доступа: Используется ли сканирование таблицы (Table Scan) или поиск по индексу (Index Seek/Scan).
  • Операции: Фильтрация (Filter), сортировка (Sort), агрегация (Hash Match), объединение (Nested Loops, Merge Join, Hash Join).
  • Оценочная стоимость: Относительная «стоимость» каждой операции в процентах, что помогает найти узкие места.
  • Оценочное число строк: Сколько строк ожидает оптимизатор на каждом шаге.

Зачем это нужно разработчику? Анализ плана выполнения — ключевой навык для оптимизации медленных запросов. Он позволяет:

  1. Найти «узкие места»: Операции с высокой стоимостью (например, Table Scan на большой таблице).
  2. Проверить использование индексов: Убедиться, что запрос использует индексы эффективно (Index Seek лучше, чем Index Scan или Table Scan).
  3. Обнаружить недостающие индексы: СУБД часто предлагает потенциально отсутствующие индексы в плане.
  4. Понять, почему запрос медленный: Из-за сложных JOIN, временных таблиц, неверных оценок кардинальности.

Как получить план запроса?

  • В SQL Server Management Studio (SSMS): Включить SET SHOWPLAN_TEXT ON; (текстовый план) или SET SHOWPLAN_XML ON; (графический план), либо нажать Ctrl + M (включить отображение плана) и выполнить запрос.
  • В коде C# / Entity Framework Core: Получить сгенерированный SQL и проанализировать его отдельно.
    using (var context = new MyDbContext())
    {
    var query = context.Orders.Where(o => o.Total > 1000);
    // Получаем SQL (EF Core 5.0+)
    var sql = query.ToQueryString();
    Console.WriteLine(sql);
    // Этот SQL можно выполнить в SSMS или другом клиенте для анализа плана
    }

    Ключевые термины для анализа:

  • Table Scan / Index Scan: Полное чтение таблицы или индекса. Может быть дорогим для больших таблиц.
  • Index Seek: Эффективный поиск по индексу с использованием предиката.
  • Key Lookup (Bookmark Lookup): Дополнительная операция для получения столбцов, не входящих в некластеризованный индекс. Часто указывает на необходимость покрывающего индекса (INCLUDE).
  • Sort: Операция сортировки в памяти или на диске (TempDB). Требует ресурсов.
  • Spill to TempDB: Когда операция (например, Hash Join) не помещается в оперативной памяти. Серьёзная проблема производительности.

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

Ответ 18+ 🔞

Давай разберемся, что это за зверь такой — план выполнения запроса. Представь, что ты дал SQL-серверу задание: «Эй, приятель, найди мне все заказы дороже тысячи баксов». А он, вместо того чтобы сразу нырнуть в таблицу, сначала садится и чертит на салфетке пошаговый план, как он это будет делать. Вот эта салфетка с каракулями — и есть план выполнения. И иногда там такой пиздец нарисован, что волосы дыбом встают.

И что же на этой салфетке намалевано?

  • Кого бить первым: В каком порядке он будет шастать по таблицам, когда делает JOIN. Иногда начинает с самой большой, а потом охуевает от количества данных.
  • Как искать: Будет ли он умно пользоваться индексом (как по алфавитному указателю в книге) или просто начнёт тупо сканировать всю таблицу с первой до последней страницы, что на больших объёмах — пиздец как долго.
  • Какие трюки выкинет: Будет ли фильтровать, сортировать, клеить таблицы через хэши или вложенные циклы.
  • Оценка затрат: Рядом с каждой операцией он пишет, насколько она, по его мнению, дорогая. Ищешь операцию с ценником в 80% — вот тебе и узкое место, скорее всего.
  • Оценка рядов: Сколько строк он ожидает найти на каждом шаге. А вот когда реальное число в десять раз больше ожидаемого — вот тут и начинается веселье, оптимизатор ебёт сам себя по мозгам.

Зачем тебе, собственно, в это всё вникать? Потому что когда запрос тормозит как черепаха в патоках, а ты не смотришь план — ты как слепой котёнок, который тыкается мордой в проблему. План — это твой рентгеновский аппарат. Он покажет:

  1. Где конкретно затык: Видишь жирную надпись Table Scan на таблице в миллион строк? Ну всё, приехали. Надо индекс делать.
  2. Работают ли твои индексы: Может, ты их создал, а запрос про них благополучно забыл и сканирует всё подряд.
  3. Чего не хватает: Умные СУБД иногда прямо в плане пишут: «Чувак, не хватает индекса вот с такими-то полями, добавь — будет тебе счастье».
  4. Почему всё так долго: Может, он из-за кривой статистики решил, что строк будет 100, а их оказалось 100000, и выбрал идиотский алгоритм соединения. В плане это видно.

Как эту салфетку-план выцарапать из сервера?

  • В SQL Server Management Studio (SSMS): Самый простой способ — перед запросом написать SET STATISTICS XML ON;, выполнить его, и тебе вывалится красивая XML-ка. Или просто нажать Ctrl+M, а потом выполнить запрос — графический план увидишь. Это как разобрать двигатель и посмотреть на каждую шестерёнку.
  • Если пишешь на C# / Entity Framework Core: Тут немного посложнее, потому что EF сама генерирует SQL. Но вытащить его можно и посмотреть план отдельно.
    using (var context = new MyDbContext())
    {
    var query = context.Orders.Where(o => o.Total > 1000);
    // Вытаскиваем SQL, который EF сгенерировала
    var sql = query.ToQueryString();
    Console.WriteLine(sql); // Копируем эту строку и лезем с ней в SSMS
    }

На какие слова в плане сразу орать «Ёбаный рот!»?

  • Table Scan / Index Scan: Полное сканирование. Если таблица большая — это пиздец, а не операция.
  • Index Seek: А вот это хорошо. Умный поиск по индексу.
  • Key Lookup (он же Bookmark Lookup): Это когда он нашёл запись по индексу, но ему нужны ещё какие-то поля, которых в индексе нет. И он пошёл искать их в основной таблице. Для каждой строчки. Если таких строчек тысячи — производительность летит в тартарары. Лечится покрывающим индексом (добавить поля в INCLUDE).
  • Sort: Сортировка. Если данных много, а памяти мало — будет больно.
  • Spill to TempDB: Вот это вообще апофеоз. Когда операция (чаще всего хэш-соединение) не влезает в оперативку и начинает срать на диск, в TempDB. Скорость падает ниже плинтуса.

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