Продвинутая оптимизация PostgreSQL и индексы
Раздел 8.11, шаг 2 из 12. Дальше — postgresql.conf.
EXPLAIN — чтение плана запроса
Планировщик PostgreSQL выбирает порядок join, тип scan и использование индексов на основе статистики (ANALYZE) и стоимостной модели (seq_page_cost, random_page_cost, cpu_* — см. шаг 3).
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= '2025-01-01'
AND o.status = 'paid';
| Опция | Зачем |
|---|---|
| ANALYZE | Реальное выполнение + фактическое время и строки |
| BUFFERS | Попадания/промахи shared buffers и read |
| VERBOSE | Имена столбцов, фильтры |
| SETTINGS | Показать нестандартные GUC на время запроса |
Типовые узлы плана:
| Узел | Смысл |
|---|---|
| Seq Scan | Полный проход таблицы — норма для малых таблиц или слабой селективности |
| Index Scan | Обход B-tree + чтение heap |
| Index Only Scan | Данные только из индекса (нужен актуальный visibility map) |
| Bitmap Index Scan + Bitmap Heap Scan | Сбор TID из индекса, затем пакетное чтение heap |
| Nested Loop / Hash Join / Merge Join | Стратегии соединения |
Если rows=1000, а actual rows=500000 — статистика устарела или нужен более точный ANALYZE, увеличение default_statistics_target или extended statistics (CREATE STATISTICS).
B-tree — индекс по умолчанию
B-tree (USING btree, тип по умолчанию) подходит для =, <, >, BETWEEN, ORDER BY, LIKE 'prefix%'.
CREATE INDEX idx_orders_created ON orders (created_at DESC);
CREATE INDEX idx_orders_status_created ON orders (status, created_at);
Порядок столбцов в составном индексе важен: индекс (status, created_at) помогает WHERE status = 'paid', но хуже — только WHERE created_at > … без фильтра по status.
GiST и SP-GiST — геоданные и нестандартные типы
GiST (Generalized Search Tree) — семейство структур для сложных типов и операторов «пересечение / содержит / рядом»:
- геометрия и PostGIS (
&&,@,<->); - полнотекстовый поиск (
@@); - диапазоны (
daterange,int4range).
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE INDEX idx_places_geom ON places USING gist (geom);
SELECT * FROM places
WHERE geom && ST_MakeEnvelope(37.5, 55.6, 37.8, 55.8, 4326);
SP-GiST (Space-Partitioned GiST) — для разреженных пространств: quad-tree, k-d tree. Используют для IP-адресов (inet), точек на карте с неравномерной плотностью.
| Тип индекса | Типичный кейс |
|---|---|
| GiST | PostGIS, tsvector, ranges |
| SP-GiST | inet, point, text prefix (альтернатива btree для некоторых паттернов) |
GIN — массивы, JSONB, полнотекст
GIN (Inverted Index) хранит ключ → список строк. Идеален, когда одно поле содержит много элементов:
- JSONB — поиск по ключам и
@>; - массивы —
@>,&&; - tsvector — полнотекст.
CREATE INDEX idx_events_payload ON events USING gin (payload jsonb_path_ops);
-- или default jsonb_ops — больше операторов, тяжелее индекс
SELECT * FROM events
WHERE payload @> '{"type": "payment", "status": "ok"}';
Подробный практикум JSONB — 3.07/66.
GIN тяжелее на запись, чем B-tree; для write-heavy таблиц оцените частоту фильтров.
BRIN — огромные последовательные таблицы
BRIN (Block Range INdex) хранит min/max (и др. summary) на диапазон блоков. Размер индекса — kilobytes на миллиарды строк, если данные физически коррелируют с ключом (логи по времени, IoT по timestamp).
CREATE INDEX idx_logs_ts ON access_logs USING brin (logged_at)
WITH (pages_per_range = 128);
BRIN бесполезен, если строки случайно разбросаны по таблице после массовых UPDATE.
Частичные индексы
Partial index индексирует только строки, удоворствующие WHERE:
CREATE INDEX idx_orders_open ON orders (customer_id, created_at)
WHERE status IN ('new', 'processing');
Плюсы — меньше размер, быстрее обновление, точнее статистика для «горячего» подмножества. Запрос должен повторять условие (или его следствие), иначе индекс не выберут.
Другие приёмы
| Приём | Пример |
|---|---|
| Covering index (INCLUDE) | CREATE INDEX … ON t (a) INCLUDE (b, c); — index-only scan без heap |
| Expression index | CREATE INDEX ON users (lower(email)); |
| Concurrent build | CREATE INDEX CONCURRENTLY — без блокировки записи (дольше, два прохода) |
Типовые ошибки
- Индекс на каждый столбец — планировщик не склеит их в один; нужен составной индекс под реальный
WHERE + ORDER BY. - Функция на столбце без expression index —
WHERE lower(email) = …не использует btree наemail. - OR across columns — часто seq scan; перепишите в
UNIONили используйте partial indexes. - OFFSET 100000 — линейная стоимость; keyset pagination (
WHERE id > $last). - N+1 из ORM — сотни мелких запросов; batch + join или
WHERE id = ANY($1).
Практика
- Возьмите медленный запрос из production (или synth 1M строк), снимите
EXPLAIN (ANALYZE, BUFFERS). - Добавьте индекс, переснимите план — сравните Buffers: shared hit/read.
- Создайте BRIN на time-series и GIN на JSONB — сравните размер индексов через
pg_indexes_size.
Связанные материалы
- Конфигурация postgresql.conf —
work_mem,random_page_cost. - Оптимизация SQL и сложные индексы — теория в разделе 3.07.
- phpPgAdmin — SQL-вкладка — запуск
EXPLAIN ANALYZEв lab. - Профилирование —
auto_explain, pgBadger. - Оконные функции — оптимизация аналитических запросов.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). MVCC, XID, снимки данных, системные поля xmin/xmax, VACUUM и autovacuum, bloat, wraparound, процессы postmaster, Shared Buffers и WAL. pg_dump, pg_basebackup, PITR, pg_probackup, Wal-G, стратегия 3-2-1, восстановление в Kubernetes и после failover. pg_stat_statements, pg_stat_activity, auto_explain, pgBadger, Prometheus postgres_exporter, типовые метрики SLA и алерты. Ansible role для установки Postgres, шаблоны postgresql.conf, Terraform для RDS и managed PostgreSQL, GitOps паттерны для инфраструктуры БД. Тонкая настройка памяти (shared_buffers, work_mem, maintenance_work_mem), I/O (effective_cache_size, random_page_cost), WAL, checkpoint и autovacuum. Документная модель в Postgres, операторы и индексы JSONB, declarative partitioning по range/list/hash, связь с оконными функциями и CTE. Хранимые функции и процедуры PL/pgSQL, row-level и statement triggers, event triggers, асинхронные события через NOTIFY и LISTEN без polling. Streaming replication, logical replication, read replicas и Hot Standby, failover, connection pooling через PgBouncer — transaction и session pooling. Официальный образ postgres, volumes для PGDATA, переменные окружения, docker-compose с healthcheck, типовые ошибки контейнеризации СУБД. Managed PostgreSQL (RDS, Cloud SQL, Yandex Managed), StatefulSet, PersistentVolume, секреты, операторы Crunchy/Zalando, anti-patterns stateful в K8s. Patroni и DCS, сравнение с Greenplum и CockroachDB, когда нужен sharding, Citus, выбор архитектуры под OLTP и аналитику. Краткое резюме раздела 8.11 — архитектура, оптимизация, эксплуатация в контейнерах и Kubernetes, HA, бэкапы и автоматизация.Архитектура PostgreSQL и внутреннее устройство
Бэкапы PostgreSQL и восстановление
Профилирование и мониторинг PostgreSQL
Автоматизация PostgreSQL — Ansible и Terraform
Конфигурация PostgreSQL — postgresql.conf
JSONB, партиционирование и расширения SQL в PostgreSQL
PL/pgSQL, триггеры и NOTIFY/LISTEN в PostgreSQL
Репликация PostgreSQL, Hot Standby и PgBouncer
PostgreSQL в Docker
PostgreSQL в облаке и Kubernetes
HA-кластеры PostgreSQL и распределённые СУБД
Практикум PostgreSQL — итоги