Перейти к содержанию

Фасет RAW

В Picodata распределенные SQL-запросы компилируются во множество локальных SQL-запросов, исполняющихся на узлах кластера. Фасет RAW позволяет увидеть каждый локальный SQL-запрос, а также соответствующий ему план исполнения.

Весь вывод фасета RAW разбит на стадии, которые пронумерованы в порядке их исполнения. В некоторых случаях стадия может быть разбита на несколько подстадий; тогда используется составная нумерация, например, 8.2.. Помимо номера, заголовок каждой стадии включает тип и место исполнения. Тело стадии содержит разнообразную информацию об исполняемом на этой стадии локальном SQL-запросе.

Пример заголовка стадии:

╭──────────────────────────╮
│ 3. Query (WHOLE STORAGE) │
╰──────────────────────────╯

Возможные типы стадий:

  • Let query — DQL-запрос, привязанный к переменной LET.
  • Return query — DQL-запрос, который возвращает строки из транзакционного блока.
  • If cond — DQL-запрос в условии IF-блока.
  • If body — DML-запрос, входящий в тело IF-блока.
  • Query — DML-запрос из транзакционного блока или DQL-запрос вне транзакционного блока.

Возможные места исполнения стадий:

  • ROUTER — запрос исполняется локально на узле-координаторе (который и называется роутером).
  • WHOLE STORAGE — запрос исполняется на каждом репликасете в кластере.
  • CONST-FILTERED STORAGE, N/M — статическая фильтрация репликасетов. Планировщику удалось до исполнения запроса вычислить конкретные репликасеты, на которые он будет отправлен. Также рядом указано количество выбранных репликасетов (N) и количество всех доступных репликасетов (M) в кластере.
  • DYN-FILTERED STORAGE — динамическая фильтрация репликасетов. Репликасеты, на которые следует отправить запрос, будут определены в процессе исполнения на основе обрабатываемых данных. В некоторых случаях может быть дополнительно указано <= N/M, если перед динамической фильтрацией бакетов удалось применить статическую фильтрацию (см. CONST-FILTERED STORAGE выше).

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

Пример плана локального SQL-запроса:

[0] SCAN TABLE testing_space (~1048576 rows)
    [0] SCAN TABLE _tmp_16164961469071853974_0136 (~1048576 rows)
[0] USE TEMP B-TREE FOR GROUP BY
[0] USE TEMP B-TREE FOR ORDER BY

Взаимодействие с фасетом BUCKETS

При одновременном использовании фасетов RAW и BUCKETS в тело стадии добавляется оценка бакетов, участвующих в исполнении локального SQL-запроса. Формат вывода совпадает с обычным применением фасета BUCKETS.

Взаимодействие с ошибками при исполнении запроса

В случае, если пользовательский запрос не удалось выполнить, Picodata вернет ошибку. В сообщении ошибки может быть указан номер стадии EXPLAIN (RAW), который позволит найти конкретный локальный SQL-запрос, который не удалось выполнить, а также другую полезную информацию.

Пример:

-- Ошибка указывает на стадию 4
ERROR:  sbroad: Query 4 from EXPLAIN (RAW): ...

Примеры

Ниже представлены примеры вывода EXPLAIN (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)

Запрос будет разослан на все репликасеты кластера, и на каждом будет произведено последовательное сканирование таблицы warehouse.

Сканирование индекса

EXPLAIN (RAW) SELECT * FROM warehouse WHERE id = 42;
╭────────────────────────────────────────╮
 1. Query (CONST-FILTERED STORAGE, 1/4) 
╰────────────────────────────────────────╯

SELECT "warehouse"."id", "warehouse"."item", "warehouse"."type" FROM "warehouse" WHERE "warehouse"."id" = CAST(42 AS int)

plan:
    [0] SEARCH TABLE warehouse USING PRIMARY KEY (id=?) (~1 row)

Из вывода следует, что запрос исполнится на одном из четырех репликасетов. Для поиска в таблице будет использован индекс первичного ключа.

Фасет RAW также отражает информацию об использовании вторичных индексов. Например:

EXPLAIN (RAW) SELECT * FROM warehouse WHERE item = 'kek';
╭──────────────────────────╮
 1. Query (WHOLE STORAGE) 
╰──────────────────────────╯

SELECT "warehouse"."id", "warehouse"."item", "warehouse"."type" FROM "warehouse" WHERE "warehouse"."item" = CAST('kek' AS string)

plan:
    [0] SEARCH TABLE warehouse USING COVERING INDEX item_idx (item=?) (~10 rows)

USING ... INDEX в выводе указывает на то, что при исполнении запроса будет задействован вторичный индекс.

Использование временной таблицы для агрегации

Для исполнения части SQL запросов Picodata материализует промежуточные данные во временную таблицу. Например:

EXPLAIN (RAW, FMT) SELECT * FROM warehouse ORDER BY 1;
 ╭──────────────────────────╮
  1. Query (WHOLE STORAGE) 
 ╰──────────────────────────╯

 SELECT
   "warehouse"."id",
   "warehouse"."item",
   "warehouse"."type"
 FROM
   "warehouse"

 plan:
     [0] SCAN TABLE warehouse (~1048576 rows)

 ╭───────────────────╮
  2. Query (ROUTER) 
 ╰───────────────────╯

 SELECT
   "COL_0" as "id",
   "COL_1" as "item",
   "COL_2" as "type"
 FROM
   (
     SELECT
       "COL_0",
       "COL_1",
       "COL_2"
     FROM
       "_tmp_6176347154012311129_0136"
   )
 ORDER BY
   1

 plan:
     [0] SCAN TABLE _tmp_6176347154012311129_0136 (~1048576 rows)
     [0] USE TEMP B-TREE FOR ORDER BY

Сначала Picodata выполнит сканирование таблицы warehouse на каждом репликасете в кластере, а далее отсортирует вернувшиеся строки на узле-координаторе, используя для этого временную таблицу.

Использование в транзакционных блоках

EXPLAIN (RAW)
DO $$ BEGIN
  LET a = (SELECT id FROM foo WHERE id = 42);
  IF a > 5 THEN
    UPDATE foo SET val = 'kek' WHERE id = 42;
  END IF;
END $$;
╭──────────────────────────────────────────╮
 1. Let "a" (CONST-FILTERED STORAGE, 1/4) 
╰──────────────────────────────────────────╯

SELECT "foo"."id" FROM "foo" WHERE "foo"."id" = CAST(42 AS int)

plan:
    [0] SEARCH TABLE foo USING PRIMARY KEY (id=?) (~1 row)

╭──────────────────────────────────────────╮
 2. If cond (CONST-FILTERED STORAGE, 1/4) 
╰──────────────────────────────────────────╯

SELECT CAST(:a AS int) > CAST(5 AS int) as "cond"

plan:
    [0] TRIVIAL

╭──────────────────────────────────────────╮
 3. If body (CONST-FILTERED STORAGE, 1/4) 
╰──────────────────────────────────────────╯

UPDATE "foo" SET "val" = CAST('kek' AS string) WHERE "foo"."id" = CAST(42 AS int)

plan:
    [0] SEARCH TABLE foo USING PRIMARY KEY (id=?) (~1 row)

В случае если LET-выражение не используется, это отражается в выводе:

EXPLAIN (RAW)
DO $$ BEGIN
  LET a = (SELECT 1);
  LET a = (SELECT id FROM foo WHERE id = 42);
  RETURN QUERY SELECT a;
END $$;
╭─────────────────────────────────────────────────────╮
 1. **Unused** let "a" (CONST-FILTERED STORAGE, 1/4) 
╰─────────────────────────────────────────────────────╯

SELECT CAST(1 AS int) as "col_1"

plan:
    [0] TRIVIAL

╭──────────────────────────────────────────╮
 2. Let "a" (CONST-FILTERED STORAGE, 1/4) 
╰──────────────────────────────────────────╯

SELECT "foo"."id" FROM "foo" WHERE "foo"."id" = CAST(42 AS int)

plan:
    [0] SEARCH TABLE foo USING PRIMARY KEY (id=?) (~1 row)

╭───────────────────────────────────────────────╮
 3. Return query (CONST-FILTERED STORAGE, 1/4) 
╰───────────────────────────────────────────────╯

SELECT CAST(:a AS int) as "col_1"

plan:
    [0] TRIVIAL

Слово **Unused** рядом с именем LET-выражения свидетельствует о том, что его значение никак не используется в транзакционном блоке. Таким образом, EXPLAIN (RAW) можно использовать для поиска "мертвого кода".