Планировщик (Planner) — это ядро оптимизатора запросов в PostgreSQL, которое превращает SQL-запрос в эффективный план выполнения.
Его задача — выбрать самый быстрый и дешёвый способ получить результат, основываясь на структуре данных, статистике и стоимости операций
Из всех возможных способов выполнить запрос выбрать тот, который минимизирует «стоимость» (cost) — оценку времени и ресурсов.
Как работает планировщик
- Получает разобранный запрос (Query Tree)
- После парсинга и анализа SQL превращается в внутреннее дерево запроса с OID’ами таблиц, типами колонок и условиями.
- Собирает информацию
- Метаданные: есть ли индексы, ограничения (
CHECK,FOREIGN KEY). - Статистика: из
pg_stats— распределение значений, число строк, частые значения. - Параметры сервера:
random_page_cost,cpu_tuple_cost,effective_cache_sizeи др.
- Генерирует возможные планы Для каждой операции рассматриваются альтернативы:
| Операция | Варианты |
|---|---|
| Чтение таблицы | Seq Scan, Index Scan, Index Only Scan, Bitmap Heap Scan |
| Соединение | Nested Loop, Hash Join, Merge Join |
| Сортировка | Sort, Index Scan (если индекс упорядочен) |
| Агрегация | HashAggregate, GroupAggregate |
- Оценивает стоимость каждого плана Использует модель cost-based optimization:
- I/O-стоимость: чтение страниц с диска или из кэша.
- CPU-стоимость: обработка строк, сравнение значений.
- Временная стоимость: сортировка, хеширование.
# Формула (упрощённо):
общая_стоимость = стоимость_чтения + (cpu_cost * число_строк)Параметры влияют на баланс:
- На SSD:
random_page_cost = 1.1(близко кseq_page_cost = 1.0) → чаще выбирает Index Scan.- На HDD:
random_page_cost = 4.0→ предпочитает Seq Scan при больших объёмах.
- Выбирает план с минимальной стоимостью
- Возвращает Plan Tree — исполняемое дерево операторов (
SeqScan,HashJoin,Sortи т.д.). - Этот план передаётся Executor’у для выполнения.
# Пример: как планировщик выбирает
# Запрос:
SELECT * FROM orders WHERE status = 'shipped';
# Планировщик проверяет:
#
# Есть ли индекс по status? → Да.
# Сколько строк с status = 'shipped'?
# Из статистики: 90% от 1 млн → 900 000 строк.
# Сравнивает:
# Index Scan: 900 000 случайных чтений → дорого.
# Scan: 1 проход по всем страницам → дешевле.
# Выбирает Seq Scan.
#
# Если бы 'shipped' было 0.1% строк → выбрал бы Index Scan.
##### Как увидеть работу планировщика?
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE status = 'shipped';
# Вывод покажет:
# - Выбранный план,
# - Оценку строк vs реальность,
# - Использование буферов.
# Если **оценка сильно отличается от реальности** — нужно `ANALYZE`.Ключевые параметры, влияющие на планировщик
| Параметр | Роль | |
|---|---|---|
random_page_cost | Относительная стоимость случайного чтения (меньше для SSD) - SSD: 1.1- NVMe: 1.0- HDD: 4.0 | |
cpu_tuple_cost | Стоимость обработки одной строки | |
effective_cache_size | Оценка кэша ОС (влияет на ожидаемое hit/miss в shared_buffers) | |
enable_* | Флаги для отключения методов (enable_seqscan = off — не рекомендуется!) | |
join_collapse_limit | Глубина перестановки JOIN’ов | |
| ------------------------ | ------------------------------------------------ | ------------------------ |
| Параметр | Нужно настраивать? | Рекомендуемое значение |
random_page_cost | Да | 1.0–1.1 (SSD/NVMe) |
effective_cache_size | Да | 50–75% от RAM |
cpu_*_cost | Редко | Оставить по умолчанию |
enable_* | Никогда | Не трогать! |
join_collapse_limit | Иногда | 8–12 (по умолчанию) |