Для того, чтобы посмотреть план запроса, используется команда EXPLAIN.
=> EXPLAIN SELECT * FROM flights;
QUERY PLAN ---------------------------------------------------------------- Seq Scan on flights (cost=0.00..4564.67 rows=214867 width=63) (1 row)
Выбран метод доступа Seq Scan - последовательное чтение.
В скобках приведены важные значения:
Стоимость указывается в некоторых условных единицах.
Приводятся два числа. Первое показывает оценку ресурсов на предварительную подготовку к операции. Для последовательного сканирования это ноль - чтобы начать возвращать данные, никакой подготовки не требуется.
Второе число показывает общую оценку ресурсов для получения всех данных. Как оно получается?
Оптимизатор PostgreSQL учитывает две компоненты: дисковый ввод-вывод и ресурсы процессора. Компонента ввода-вывода рассчитывается как произведение числа страниц в таблице на условную стоимость чтения одной страницы:
=> SELECT relpages, current_setting('seq_page_cost'), relpages * current_setting('seq_page_cost')::real AS total FROM pg_class WHERE relname='flights';
relpages | current_setting | total ----------+-----------------+------- 2416 | 1 | 2416 (1 row)
Вторая компонента - ресурсы процессора - складывается из стоимости обработки каждой строки:
=> SELECT reltuples, current_setting('cpu_tuple_cost'), reltuples * current_setting('cpu_tuple_cost')::real AS total FROM pg_class WHERE relname='flights';
reltuples | current_setting | total -----------+-----------------+--------- 214867 | 0.01 | 2148.67 (1 row)
Рассмотрим более сложный запрос с агрегатной функцией (на небольшой таблице):
=> EXPLAIN SELECT count(*) FROM seats;
QUERY PLAN --------------------------------------------------------------- Aggregate (cost=24.74..24.75 rows=1 width=8) -> Seq Scan on seats (cost=0.00..21.39 rows=1339 width=0) (2 rows)
План состоит из двух узлов. Верхний - Aggregate, в котором происходит вычисление count, - получает данные от нижнего - Seq Scan.
Обратите внимание на стоимость узла Aggregate: нижняя цифра практически равна верхней. Это означает, что узел не может выдать результат, пока не обработает все данные (что вполне логично).
Разница между оценкой для Aggregate и верхней оценкой для Seq Scan - стоимость работы собственно узла Aggregate. Она вычисляется исходя из оценки ресурсов на выполнение условной операции:
=> SELECT reltuples, current_setting('cpu_operator_cost'), reltuples * current_setting('cpu_operator_cost')::real AS total FROM pg_class WHERE relname='seats';
reltuples | current_setting | total -----------+-----------------+-------- 1339 | 0.0025 | 3.3475 (1 row)
Планировщик не будет рассматривать параллельные планы для таблиц размером меньше, чем
=> SELECT current_setting('min_parallel_table_scan_size');
current_setting ----------------- 8MB (1 row)
Поэтому обратимся к большой таблице:
=> SELECT pg_size_pretty(pg_table_size('bookings')) size, (SELECT count(*) FROM bookings) cnt;
size | cnt --------+--------- 105 MB | 2111110 (1 row)
=> EXPLAIN SELECT count(*) FROM bookings;
QUERY PLAN -------------------------------------------------------------------------------------------- Finalize Aggregate (cost=25442.58..25442.59 rows=1 width=8) -> Gather (cost=25442.36..25442.57 rows=2 width=8) Workers Planned: 2 -> Partial Aggregate (cost=24442.36..24442.37 rows=1 width=8) -> Parallel Seq Scan on bookings (cost=0.00..22243.29 rows=879629 width=0) (5 rows)
Все, что находится ниже узла Gather - параллельная часть плана. Он выполняется в каждом из рабочих процессов (которых запланировано 2) и, возможно, в ведущем процессе.
Узел Gather и все узлы выше выполняются только в ведущем процессе. Это последовательная часть плана.
Начнем разбираться снизу вверх. Узел Parallel Seq Scan представляет сканирование таблицы в параллельном режиме.
В поле rows показана оценка числа строк, которые обработает один рабочий процесс. Всего их запланировано 2, и еще часть работы выполнит ведущий, поэтому общее число строк делится на 2.4 (доля ведущего процесса уменьшается с ростом числа рабочих процессов).
=> SELECT round(reltuples / 2.4) FROM pg_class WHERE relname = 'bookings';
round -------- 879629 (1 row)
Для оценки узла Parallel Seq Scan компонента ввода-вывода берется полностью (таблицу все равно придется прочитать страница за страницей), а ресурсы процессора делятся между процессами (на 2.4 в данном случае).
=> SELECT round( relpages * current_setting('seq_page_cost')::real + reltuples * current_setting('cpu_tuple_cost')::real / 2.4 ) FROM pg_class WHERE relname = 'bookings';
round ------- 22243 (1 row)
=> EXPLAIN SELECT count(*) FROM bookings;
QUERY PLAN -------------------------------------------------------------------------------------------- Finalize Aggregate (cost=25442.58..25442.59 rows=1 width=8) -> Gather (cost=25442.36..25442.57 rows=2 width=8) Workers Planned: 2 -> Partial Aggregate (cost=24442.36..24442.37 rows=1 width=8) -> Parallel Seq Scan on bookings (cost=0.00..22243.29 rows=879629 width=0) (5 rows)
Следующий узел - Partial Aggregate - выполняет агрегацию данных, полученных рабочим процессом, то есть в данном случае подсчитывает количество строк. Оценка выполняется уже известным образом (и добавляется к оценке сканирования таблицы):
=> SELECT round(reltuples / 2.4 * current_setting('cpu_operator_cost')::real) FROM pg_class WHERE relname='bookings';
round ------- 2199 (1 row)
Следующий узел - Gather - выполняется на ведущем процессе. Он отвечает за запуск рабочих процессов и получение от них данных.
Запуск процессов и пересылка каждой строки данных оцениваются как
=> SELECT current_setting('parallel_setup_cost') parallel_setup_cost, current_setting('parallel_tuple_cost') parallel_tuple_cost;
parallel_setup_cost | parallel_tuple_cost ---------------------+--------------------- 1000 | 0.1 (1 row)
В данном случае пересылается всего одна строка и основная стоимость приходится на запуск.
=> EXPLAIN SELECT count(*) FROM bookings;
QUERY PLAN -------------------------------------------------------------------------------------------- Finalize Aggregate (cost=25442.58..25442.59 rows=1 width=8) -> Gather (cost=25442.36..25442.57 rows=2 width=8) Workers Planned: 2 -> Partial Aggregate (cost=24442.36..24442.37 rows=1 width=8) -> Parallel Seq Scan on bookings (cost=0.00..22243.29 rows=879629 width=0) (5 rows)
Последний узел - Finalize Aggregate - агрегирует полученные частичные агрегаты. Поскольку для этого надо сложить всего три числа, оценка минимальна.
Конец демонстрации.