EXPLAIN¶
Для анализа производительности запросов в Picodata можно использовать команду
EXPLAIN, с помощью которой можно узнать, как будет выглядеть план исполнения
запроса. План позволяет наглядно оценить структуру и последовательность действий
при выполнении запроса.
В реализацию EXPLAIN заложены следующие принципы:
- Фасеты (режимы отображения)
- Достоверность плана
- Возможность применения к проблемным запросам
- Возможности форматирования
- Подробный вывод
Синтаксис¶
Фасеты (режимы отображения)¶
Команда EXPLAIN позволяет указывать определенные режимы вывода EXPLAIN,
также называемые фасетами. В настоящий момент можно указывать фасеты RAW,
LOGICAL, BUCKETS, FORWARD и CONTEXT в любом порядке. Каждый фасет
отвечает за конкретную информацию о запросе.
При указании нескольких фасетов одновременно также печатаются их заголовки, позволяющие быстро ориентироваться в выводе. Например:
EXPLAIN (LOGICAL, BUCKETS) SELECT name, id FROM _pico_table;
──────────────────────────────────────────────────────────────────────
# Logical plan
──────────────────────────────────────────────────────────────────────
projection (_pico_table.name::text -> name, _pico_table.id::int -> id)
scan _pico_table
──────────────────────────────────────────────────────────────────────
# Buckets
──────────────────────────────────────────────────────────────────────
buckets = any
Если для EXPLAIN не указано ни одного фасета, то фасетами по умолчанию являются
LOGICAL и BUCKETS. Таким образом, запросы
EXPLAIN (LOGICAL, BUCKETS) SELECT name, id FROM _pico_table;
и
EXPLAIN SELECT name, id FROM _pico_table;
эквивалентны.
С подробным описанием каждого фасета EXPLAIN можно ознакомиться тут:
- RAW — низкоуровневый план исполнения запроса.
- LOGICAL — высокоуровневый план исполнения запроса.
- BUCKETS — информация о бакетах, участвующих в исполнении запроса.
- FORWARD — анализ сетевых пересылок, необходимых для исполнения запроса.
- CONTEXT — прочие переменные среды, влияющие на исполнение запроса.
Достоверность плана¶
Команда EXPLAIN в Picodata показывает полный план запроса — со всеми операциями,
которые реально выполняются, даже если они не следуют напрямую из текста
запроса. Например, COUNT(*) в Picodata исполняется путем частичного подсчета
на узлах хранения и финального суммирования результатов. Это следует из вывода
фасета LOGICAL:
EXPLAIN (LOGICAL) SELECT COUNT(*) FROM t;
projection (sum(count_1::int::int)::int::int -> col_1)
motion [policy: full, program: ReshardIfNeeded]
projection (count(*)::int -> count_1)
scan t
Узел motion отражает перераспределение данных между узлами кластера. Подробнее
об этом можно почитать здесь.
Возможность применения к проблемным запросам¶
Важной особенностью EXPLAIN в Picodata является возможность применить его
к запросам, которые приводят к ошибке при исполнении. Так, Picodata всегда
старается построить EXPLAIN для синтаксически корректных запросов, даже если они
семантически некорректные. Например:
SELECT name, id FROM _pico_table WHERE MAX(id) = 5;
ERROR: Query 1 from EXPLAIN (RAW): Failed to compile SQL statement: misuse of aggregate function MAX()
Тем не менее, Picodata выполнит EXPLAIN от такого запроса:
EXPLAIN (RAW) SELECT name, id FROM _pico_table WHERE MAX(id) = 5;
╭───────────────────╮
│ 1. Query (ROUTER) │
╰───────────────────╯
SELECT "_pico_table"."name", "_pico_table"."id" FROM "_pico_table" WHERE max ("_pico_table"."id"::int) = 5::int
plan:
Failed to compile SQL statement: misuse of aggregate function MAX()
То же относится к транзакционным блокам, запросы которых затрагивают разные бакеты. В текущей реализации транзакционный блок может затрагивать только один бакет, поэтому следующий блок исполнить нельзя:
DO $$ BEGIN
RETURN QUERY SELECT * FROM foo WHERE id = 1;
DELETE FROM foo WHERE id = 2;
END $$;
ERROR: statement 1 (RETURN QUERY) and statement 2 (DML): different buckets: [1934] and [1410]
Тем не менее, при помощи EXPLAIN и его фасетов
RAW и
BUCKETS
можно обнаружить, какие бакеты потребовались каждому запросу:
EXPLAIN (RAW, BUCKETS)
DO $$ BEGIN
RETURN QUERY SELECT * FROM foo WHERE id = 1;
DELETE FROM foo WHERE id = 2;
END $$;
──────────────────────────────────────────────────────────────────────
# Raw plan
──────────────────────────────────────────────────────────────────────
╭───────────────────────────────────────────────╮
│ 1. Return query (CONST-FILTERED STORAGE, 1/4) │
╰───────────────────────────────────────────────╯
SELECT "foo"."id" FROM "foo" WHERE "foo"."id" = 1::int
plan:
[0] SEARCH TABLE foo USING PRIMARY KEY (id=?) (~1 row)
buckets = [1934]
╭────────────────────────────────────────╮
│ 2. Query (CONST-FILTERED STORAGE, 1/4) │
╰────────────────────────────────────────╯
DELETE FROM "foo" WHERE "foo"."id" = 2::int
plan:
[0] SEARCH TABLE foo USING PRIMARY KEY (id=?) (~1 row)
buckets = [1410]
──────────────────────────────────────────────────────────────────────
# Buckets
──────────────────────────────────────────────────────────────────────
buckets = [1410, 1934]
EXPLAIN применим и к транзакционным блокам с запросами, которые нельзя
выполнить в транзакции. Часто это запросы, требующие перемещения
данных (см.
ограничения). Вместо локального SQL
такого запроса выводится причина, по которой его нельзя выполнить:
EXPLAIN (RAW)
DO $$ BEGIN
RETURN QUERY SELECT * FROM foo ORDER BY id;
END $$;
╭─────────────────────────────────╮
│ 1. Return query (WHOLE STORAGE) │
╰─────────────────────────────────╯
**Error**: cannot generate SQL: the query requires cross-shard data movement
Фасет LOGICAL выводит ту же причину вместе с
логическим планом запроса. Из плана видно, где именно происходит перемещение
данных.
Возможности форматирования¶
В Picodata для EXPLAIN также существует опция FMT, которая применяется к
фасетам. В случае LOGICAL форматирование применяется к плану. В случае RAW
форматирование применяется к локальным SQL-запросам и их планам. В случае
BUCKETS список бакетов разбивается на строки, но только вместе с опцией
VERBOSE. Ниже приведены примеры.
Пример с LOGICAL:
EXPLAIN (LOGICAL) SELECT id, item FROM warehouse;
projection (warehouse.id::int -> id, warehouse.item::text -> item)
scan warehouse
При указании опции FMT вывод форматируется:
EXPLAIN (LOGICAL, FMT) SELECT id, item FROM warehouse;
projection (
warehouse.id::int -> id,
warehouse.item::text -> item
)
scan warehouse
Пример с RAW:
EXPLAIN (RAW) SELECT * FROM warehouse;
╭──────────────────────────╮
│ 1. Query (WHOLE STORAGE) │
╰──────────────────────────╯
SELECT "warehouse"."id", "warehouse"."item", "warehouse"."type" FROM "warehouse"
plan:
[0] SCAN TABLE warehouse (~1048576 rows)
При указании опции FMT вывод будет отформатирован:
EXPLAIN (RAW, FMT) SELECT * FROM warehouse;
╭──────────────────────────╮
│ 1. Query (WHOLE STORAGE) │
╰──────────────────────────╯
SELECT
"warehouse"."id",
"warehouse"."item",
"warehouse"."type"
FROM
"warehouse"
plan:
[0] SCAN TABLE warehouse (~1048576 rows)
Пример с BUCKETS (опция VERBOSE выводит список бакетов
целиком):
EXPLAIN (BUCKETS, VERBOSE) SELECT * FROM warehouse
WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16);
buckets = [219, 626, 653, 799, 1403, 1410, 1418, 1860, 1934, 1948, 1958, 2564, 2640, 2752, 2802, 2852]
При указании опции FMT список бакетов будет отформатирован:
EXPLAIN (BUCKETS, VERBOSE, FMT) SELECT * FROM warehouse
WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16);
buckets = [
219, 626, 653, 799, 1403, 1410, 1418, 1860,
1934, 1948, 1958, 2564, 2640, 2752, 2802,
2852
]
Списки, которые умещаются в одну строку, не разбиваются.
Опцию FMT полезно использовать при анализе вывода EXPLAIN для сложных запросов.
В будущем могут быть добавлены как новые фасеты, так и новые способы
форматирования их элементов.
Подробный вывод¶
Для EXPLAIN также существует опция VERBOSE, которая управляет полнотой
вывода фасетов. В настоящий момент она применяется только к фасету BUCKETS.
По умолчанию этот фасет выводит лишь начало списка бакетов — столько элементов,
сколько умещается в одну строку, — и указывает, сколько бакетов осталось за
ними. Опция VERBOSE выводит список целиком. Ниже приведен пример.
Пример с BUCKETS:
EXPLAIN (BUCKETS) SELECT * FROM warehouse
WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16);
buckets = [219, 626, 653, 799, 1403, ... (11 more)]
При указании опции VERBOSE список бакетов выводится целиком:
EXPLAIN (BUCKETS, VERBOSE) SELECT * FROM warehouse
WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16);
buckets = [219, 626, 653, 799, 1403, 1410, 1418, 1860, 1934, 1948, 1958, 2564, 2640, 2752, 2802, 2852]
Опцию VERBOSE полезно использовать, когда нужно узнать точный набор бакетов,
на которых исполняется запрос. Вместе с опцией FMT полный
список бакетов будет отформатирован. Если опция VERBOSE не указана, то FMT
не применяется, т.к. список бакетов короткий и не нуждается в форматировании.