Learning Platform
Глоссарий Troubleshooting
Урок 05.06 · 23 мин
Средний
query-lifecycleexplainexplain-analyzequery-plan

EXPLAIN и EXPLAIN ANALYZE

Пять предыдущих уроков прошли жизненный цикл запроса изнутри: текст, AST, Analysis, логический план, оптимизация, распределённый план. Всё это — внутренние представления. Но Trino даёт инструмент, который показывает эти представления наружу, — команду EXPLAIN. Это главный инструмент диагностики: умение читать план отличает того, кто пишет SQL, от того, кто понимает, что движок с этим SQL делает.

Этот урок — про EXPLAIN и EXPLAIN ANALYZE: что показывает каждая команда, как читать вывод и в чём принципиальная разница между ними.


EXPLAIN: план без выполнения

EXPLAIN показывает план запроса, не выполняя сам запрос. Trino проходит этапы планирования — анализ, логический план, оптимизация, распределённый план — и выдаёт результат, но до исполнения дело не доходит. Никакие данные не читаются, воркеры не нагружаются.

У EXPLAIN есть параметр TYPE, выбирающий, какое представление показать:

КомандаЧто показывает
EXPLAIN (TYPE LOGICAL)Логический план — дерево PlanNode после оптимизации
EXPLAIN (TYPE DISTRIBUTED)Распределённый план — фрагменты-stages с типами распределения
EXPLAIN (TYPE VALIDATE)Только проверка: корректен ли запрос (true/false), без построения плана
EXPLAIN (TYPE IO)Какие таблицы и столбцы запрос будет читать и писать

EXPLAIN без указания TYPE показывает распределённый план — это поведение по умолчанию.

EXPLAIN показывает этапы планирования наружу
TYPE LOGICALДерево PlanNode после iterative optimizer — операции над данными.
TYPE DISTRIBUTEDФрагменты-stages с типами распределения — результат distributed planning.
TYPE VALIDATEТолько ответ, корректен ли запрос — без построения плана.

Главное про EXPLAIN: цифры в его выводе — это оценки (estimates). Когда EXPLAIN пишет рядом с узлом число строк, это прогноз планировщика, основанный на статистике таблиц. Прогноз может быть точным, а может сильно ошибаться, если статистики нет или она устарела. EXPLAIN показывает намерение движка, а не факт.

EXPLAIN в PostgreSQL: чтение плана запроса ClickHouse EXPLAIN: четыре варианта анализа запросов

Как читать логический план

Возьмём вывод EXPLAIN (TYPE LOGICAL):

EXPLAIN (TYPE LOGICAL)
SELECT name FROM tpch.sf1.customer WHERE acctbal > 5000;
Output[columnNames = [name]]
    Project[]
        Filter[filterPredicate = (acctbal > 5000)]
            TableScan[table = tpch:customer:sf1]
                name := name:varchar(25)
                acctbal := acctbal:double

Читается это дерево по отступам — отступ показывает вложенность, родитель левее потомка. И читать его смысл удобнее снизу вверх, по потоку данных:

  1. TableScan — лист. Читает таблицу customer, столбцы name и acctbal.
  2. Filter — над ним. Применяет предикат acctbal > 5000, отбрасывает строки.
  3. Project — выбирает финальный набор столбцов.
  4. Output — корень. Отдаёт результат, столбец name.

Это ровно дерево PlanNode из урока про логическое планирование, выведенное текстом. Обратите внимание: фильтр стоит прямо над TableScan — это работа оптимизатора, опустившего предикат к источнику.


Как читать распределённый план

EXPLAIN (TYPE DISTRIBUTED) показывает план, разрезанный на фрагменты:

EXPLAIN (TYPE DISTRIBUTED)
SELECT nationkey, count(*) AS cnt
FROM tpch.sf1.customer
GROUP BY nationkey;
Fragment 0 [SINGLE]
    Output[nationkey, cnt]
        RemoteSource[sourceFragmentIds = [1]]

Fragment 1 [HASH]
    Aggregate[type = FINAL, keys = [nationkey]]
        cnt := count(partial_cnt)
        RemoteSource[sourceFragmentIds = [2]]

Fragment 2 [SOURCE]
    Aggregate[type = PARTIAL, keys = [nationkey]]
        partial_cnt := count(*)
        TableScan[table = tpch:customer:sf1]

Здесь три фрагмента, и читать тоже удобно снизу — от наибольшего номера:

  • Fragment 2 [SOURCE] — чтение customer и частичная агрегация (PARTIAL) прямо у источника: каждый воркер считает count по своей части данных. Тип SOURCE — фрагмент идёт туда, где splits.
  • Fragment 1 [HASH] — данные перераспределены по хэшу nationkey, считается финальная агрегация (FINAL): частичные counts суммируются по каждой группе.
  • Fragment 0 [SINGLE] — собирает результат на одной ноде, отдаёт клиенту.

RemoteSource — это линия разреза между фрагментами, точка сетевого обмена. sourceFragmentIds = [2] означает «данные приходят от фрагмента 2».

Здесь же видна важная оптимизация — двухфазная агрегация. Агрегат считается дважды: PARTIAL локально у источника и FINAL после обмена. Смысл — резко уменьшить объём данных, идущих по сети: вместо миллионов строк между фрагментами летят уже частично свёрнутые счётчики по группам.

NOTE

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


EXPLAIN ANALYZE: план плюс факт

EXPLAIN ANALYZE — принципиально другая команда. Она реально выполняет запрос и показывает распределённый план, заполненный фактической статистикой исполнения.

Разница с EXPLAIN фундаментальна:

EXPLAIN против EXPLAIN ANALYZE
EXPLAINЗапрос НЕ выполняется. Цифры — оценки планировщика по статистике. Мгновенно, ресурсы кластера не тратятся.
против
EXPLAIN ANALYZEЗапрос ВЫПОЛНЯЕТСЯ полностью. Цифры — фактические: реальное время, реальные строки.

EXPLAIN отвечает на вопрос «что движок СОБИРАЕТСЯ делать». EXPLAIN ANALYZE отвечает на вопрос «что на самом деле произошло». Поскольку EXPLAIN ANALYZE выполняет запрос, он занимает столько же ресурсов и времени, сколько обычный запуск, — и не годится для тяжёлых запросов, которые не хочется выполнять. Зато его цифры реальны.

Что показывает EXPLAIN ANALYZE сверх обычного плана:

  • Фактическое время по стадиям и операторам — где запрос реально провёл время (wall time, CPU time).
  • Фактическое число строк, прошедших через каждый оператор, — и его можно сравнить с оценкой из EXPLAIN.
  • Объёмы данных: сколько прочитано, сколько передано между фрагментами по сети.
  • Относительная стоимость стадий — какая стадия съела львиную долю.
EXPLAIN ANALYZE
SELECT nationkey, count(*) FROM tpch.sf1.customer GROUP BY nationkey;
Fragment 1 [HASH]
    CPU: 2.34s, Scheduled: 4.10s, Input: 150000 rows
    Aggregate[type = FINAL, keys = [nationkey]]
        Input avg.: 25.00 rows, Input std.dev.: 12.5%
...
Fragment 2 [SOURCE]
    CPU: 5.81s, Scheduled: 9.20s, Input: 1500000 rows
    TableScan[table = tpch:customer:sf1]
        Input: 1500000 rows

Здесь видно фактическое: Fragment 2 прочитал 1 500 000 строк и потратил 5.81s CPU, Fragment 1 после агрегации работает уже со 150 000 строк. Это не прогноз — это то, что случилось.


Зачем это нужно: связь оценки и факта

Главная диагностическая ценность — в сопоставлении EXPLAIN и EXPLAIN ANALYZE. EXPLAIN показывает оценку строк, EXPLAIN ANALYZE — фактическое число. Если они близки — статистика хорошая, планировщик видел реальность, план построен на верных данных. Если они расходятся на порядки — статистика врёт, и любые решения оптимизатора (порядок join, broadcast или hash) могли быть приняты на ложной основе.

Это типичный сценарий разбора медленного запроса. Снимаете EXPLAIN ANALYZE, находите стадию с наибольшим временем — bottleneck. Смотрите фактическое число строк на ней. Сравниваете с оценкой из EXPLAIN. Большое расхождение — сигнал собрать статистику командой ANALYZE и перепланировать. Это смыкается с модулем про cost-based optimizer: качество плана определяется качеством статистики, а EXPLAIN/EXPLAIN ANALYZE — инструмент, которым это качество проверяют.

TIP

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


Место EXPLAIN в картине цикла

EXPLAIN — не отдельный этап жизненного цикла, а окно в него. Он показывает наружу те представления, которые движок строит внутри: TYPE LOGICAL — результат логического планирования и оптимизации, TYPE DISTRIBUTED — результат distributed planning. EXPLAIN ANALYZE идёт дальше и показывает результат самого исполнения.

Поэтому этот урок завершает модуль про жизненный цикл логично: пять уроков объясняли этапы изнутри, шестой дал инструмент видеть их снаружи. Дальше курс переходит к распределённому исполнению — к тому, что происходит после планирования, когда stages разворачиваются в task на воркерах. И EXPLAIN ANALYZE будет главным инструментом и там.


Попробуй сам

На кластере Trino отработайте оба инструмента:

  1. Снимите EXPLAIN (TYPE LOGICAL) и EXPLAIN (TYPE DISTRIBUTED) для одного и того же запроса с GROUP BY. Сопоставьте: как узлы логического плана разложились по фрагментам распределённого.
  2. Снимите EXPLAIN ANALYZE для того же запроса. Найдите стадию с наибольшим CPU-временем. Найдите фактическое число строк в TableScan.
  3. Сравните оценку числа строк из EXPLAIN с фактом из EXPLAIN ANALYZE для tpch.sf1.orders. У tpch статистика обычно хорошая — оценки должны быть близки к факту.
  4. Возьмите запрос с join и по распределённому плану определите: какой фрагмент читает какую таблицу, где проходят RemoteSource, какой тип у фрагмента join.
  5. Сформулируйте письменно, в каких ситуациях вы выберете EXPLAIN, а в каких — EXPLAIN ANALYZE, и почему.

Проверка знанийKnowledge check
В чём принципиальная разница между EXPLAIN и EXPLAIN ANALYZE, и почему сопоставление их вывода — главный приём диагностики плохого плана?
ОтветAnswer
Разница принципиальная. EXPLAIN показывает план запроса, НЕ выполняя сам запрос: Trino проходит этапы планирования и выдаёт результат, но данные не читаются и воркеры не нагружаются; цифры в выводе EXPLAIN — это оценки планировщика, прогноз на основе статистики таблиц, который может быть точным или сильно ошибаться. EXPLAIN отвечает на вопрос что движок собирается делать. EXPLAIN ANALYZE, наоборот, реально выполняет запрос и показывает распределённый план, заполненный фактической статистикой исполнения: реальное CPU-время и wall time по стадиям и операторам, фактическое число прошедших строк, реальные объёмы данных и сетевого обмена. EXPLAIN ANALYZE отвечает на вопрос что на самом деле произошло, и поэтому занимает столько же ресурсов, сколько обычный запуск. Сопоставление их вывода — главный приём диагностики, потому что качество плана определяется качеством статистики. EXPLAIN даёт оценку числа строк, EXPLAIN ANALYZE — фактическое число. Если они близки — статистика хорошая, планировщик видел реальность. Если расходятся на порядки — статистика врёт, и решения оптимизатора (порядок join, broadcast или hash) были приняты на ложной основе; это сигнал собрать статистику командой ANALYZE и перепланировать запрос.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. В чём принципиальная разница между EXPLAIN и EXPLAIN ANALYZE?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6