EXPLAIN: четыре варианта анализа запросов
Когда запрос работает медленно, первый вопрос — “почему?”. EXPLAIN — это диагностический инструмент, который отвечает на этот вопрос без фактического выполнения запроса (кроме EXPLAIN PLAN с некоторыми настройками). В ClickHouse четыре основных варианта EXPLAIN, каждый раскрывает разный аспект выполнения запроса. В Module 06 урок 02 мы детально разобрали EXPLAIN PIPELINE для понимания процессорного DAG — здесь фокус на все четыре варианта с точки зрения практического performance tuning.
Четыре варианта EXPLAIN
| Вариант | Что показывает | Ключевые опции | Когда использовать |
|---|---|---|---|
| EXPLAIN PLAN | Логический план: Expression, Filter, ReadFromMergeTree | indexes=1, header=1, description=1 | Диагностика индексов, порядок JOIN |
| EXPLAIN PIPELINE | Физический DAG процессоров с x N потоками | graph=1 (DOT), compact=0 | Анализ параллелизма, поиск bottleneck |
| EXPLAIN ESTIMATE | Оценка rows/marks/parts без выполнения | — | Быстрая проверка объёма до запуска |
| EXPLAIN SYNTAX | Нормализованный SQL после оптимизаций | oneline=1 | Отладка: что видит оптимизатор |
EXPLAIN PLAN: логический план с индексами
EXPLAIN PLAN (сокращённо EXPLAIN) показывает логическую структуру запроса. Наиболее полезная опция — indexes=1: она раскрывает, какие индексы использовались для pruning.
-- Логический план с анализом индексов
EXPLAIN PLAN indexes = 1
SELECT max(price)
FROM uk_price_paid
WHERE town = 'LONDON';
Типичный вывод:
Expression (Projection)
Expression (Before ORDER BY)
Aggregating
Expression (Before GROUP BY)
Filter
ReadFromMergeTree (uk.uk_price_paid)
Indexes:
PrimaryKey
Keys: town
Condition: (town in ['LONDON', 'LONDON'])
Parts: 2/12
Granules: 41/480
Что читать в выводе:
Parts: 2/12— primary key отсёк 10 из 12 partsGranules: 41/480— из 480 гранул будут читаться только 41- Если видите
Parts: 12/12— primary key не помогает, возможно нужен skip-индекс или переосмысление ORDER BY
Дополнительные опции:
-- Показать заголовки столбцов
EXPLAIN PLAN header = 1
SELECT count(), sum(price) FROM uk_price_paid;
-- Детальные описания узлов
EXPLAIN PLAN description = 1
SELECT * FROM uk_price_paid WHERE town = 'LONDON' LIMIT 10;
EXPLAIN PIPELINE: физический граф процессоров
EXPLAIN PIPELINE показывает, как запрос будет выполнен физически: цепочку процессоров, параллелизм и переходы между потоками. Этот вариант детально рассматривался в Module 06 урок 02 — здесь сосредоточимся на применении для performance tuning.
-- Физический граф параллельного выполнения
EXPLAIN PIPELINE
SELECT max(price) FROM uk_price_paid;
Пример вывода:
(Expression)
ExpressionTransform
(Aggregating)
Resize 4 → 1
AggregatingTransform × 4
(Expression)
ExpressionTransform × 4
(ReadFromMergeTree)
MergeTreeSelect(pool: ReadPoolInOrder) × 4
EXPLAIN PIPELINE может показать меньше потоков, чем задано в max_threads. Это адаптивный параллелизм: при малом объёме данных ClickHouse выделяет только столько потоков, сколько нужно для покрытия доступных гранул. Если max_threads=16, но таблица содержит 4 гранулы — вы увидите x 4, а не x 16. Всегда проверяйте на репрезентативном объёме данных.
EXPLAIN ESTIMATE: оценка без выполнения
EXPLAIN ESTIMATE — самый быстрый способ понять масштаб запроса. Он читает только метаданные parts, не обращаясь к данным:
-- Оценка: сколько гранул будет прочитано?
EXPLAIN ESTIMATE
SELECT max(price)
FROM uk_price_paid
WHERE town = 'LONDON';
Вывод:
database table parts rows marks
uk uk_price_paid 2 1048576 41
Интерпретация:
parts: 2— partition pruning сработал, из 12 parts читается 2marks: 41— будет прочитано 41 гранула (41 * 8192 строк = ~336K строк)rows: 1048576— оценочное количество строк (по метаданным, не точное)
Используйте EXPLAIN ESTIMATE перед запуском тяжёлого запроса на продакшн-данных, чтобы убедиться, что фильтры работают корректно.
EXPLAIN SYNTAX: нормализация запроса
EXPLAIN SYNTAX показывает запрос после всех автоматических оптимизаций — alias resolution, удаление тавтологий, PREWHERE promotion. Это то, что реально видит оптимизатор:
-- Как оптимизатор видит запрос?
EXPLAIN SYNTAX
SELECT 1 + 1 AS x, x * 2;
Вывод:
SELECT
(1 + 1) AS x,
(1 + 1) * 2
Алиас x был заменён исходным выражением — оптимизатор всегда работает с развёрнутыми выражениями.
Практический пример с PREWHERE:
-- Запрос с WHERE
EXPLAIN SYNTAX
SELECT price FROM uk_price_paid WHERE town = 'LONDON' AND price > 500000;
Если optimize_move_to_prewhere=1 (по умолчанию включено), вывод покажет автоматическое перемещение части условий в PREWHERE для снижения I/O.
Новый анализатор (New Analyzer, ClickHouse 24.3+)
Начиная с ClickHouse 24.3, новый анализатор включён по умолчанию. Он существенно меняет форму вывода EXPLAIN PLAN: AST-rewrites и оптимизации происходят до построения логического плана, поэтому вывод может выглядеть иначе по сравнению со старым анализатором.
Для отладки трансформаций нового анализатора используется отдельный вариант:
-- Дерево запроса после passes нового анализатора
EXPLAIN QUERY TREE run_passes = 1
SELECT count() FROM uk_price_paid WHERE town = 'LONDON';
Это полезно при миграции сложных запросов или отладке неожиданного поведения оптимизатора после обновления на 24.3+.
Ключевые выводы
- EXPLAIN PLAN indexes=1 — главный инструмент диагностики индексов. Если Parts: N/N — primary key не помогает.
- EXPLAIN PIPELINE показывает физический параллелизм. Адаптивный параллелизм может показать меньше потоков, чем max_threads — это нормально при малом объёме данных.
- EXPLAIN ESTIMATE — мгновенная оценка масштаба запроса по метаданным. Используйте до запуска тяжёлых запросов.
- EXPLAIN SYNTAX раскрывает, что видит оптимизатор: развёрнутые алиасы, PREWHERE promotion, удалённые тавтологии.
- Новый анализатор (24.3+) меняет форму вывода EXPLAIN PLAN — AST-rewrites происходят до построения плана. Для отладки анализатора используйте EXPLAIN QUERY TREE.