PostgreSQL остаётся одной из самых популярных реляционных СУБД, но с ростом данных и нагрузки даже опытные администраторы сталкиваются с деградацией производительности. По данным Stack Overflow Developer Survey 2025, PostgreSQL занимает первое место среди баз данных по использованию — более 45% разработчиков выбирают её для продакшена. Однако многие не используют весь потенциал оптимизации: неправильные индексы, отсутствие партиционирования и игнорирование EXPLAIN ANALYZE приводят к таймаутам и высокому CPU. В этой подборке я собрал 11 промтов, которые помогут вам быстро диагностировать и устранять узкие места. Они разделены на три категории: базовые (для начинающих), продвинутые (для опытных) и экспертные (для архитекторов и DBA). Каждый промт сопровождается примером и пояснением. Все примеры проверены на PostgreSQL 16 и 17.
Базовые промты
1. Анализ медленных запросов через pg_stat_statements
Задача: Найти топ-5 самых ресурсоёмких запросов за последний час.
Промт:
SELECT query, calls, total_exec_time, mean_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 5;
Пример результата:
| query | calls | total_exec_time | mean_exec_time | rows |
|---|---|---|---|---|
| SELECT * FROM orders WHERE user_id = $1 | 15000 | 45000.2 | 3.0 | 15000 |
| UPDATE products SET stock = stock - 1 WHERE id = $1 | 8000 | 12000.5 | 1.5 | 8000 |
Пояснение: pg_stat_statements — официальное расширение (доступно с PostgreSQL 9.2). Включается в postgresql.conf: shared_preload_libraries = 'pg_stat_statements'. Запросы с высоким total_exec_time — кандидаты на оптимизацию.
2. Чтение плана выполнения с EXPLAIN ANALYZE
Задача: Понять, почему запрос выполняется 2 секунды.
Промт:
EXPLAIN (ANALYZE, BUFFERS)
SELECT o.id, o.total, u.name
FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.created_at > '2026-01-01' AND u.status = 'active';
Пример результата:
Hash Join (cost=1000.00..20000.00 rows=5000 width=36) (actual time=1500.00..2000.00 rows=5000 loops=1)
Hash Cond: (o.user_id = u.id)
Buffers: shared hit=10000 read=5000
-> Seq Scan on orders o (cost=0.00..15000.00 rows=5000 width=16) (actual time=0.05..800.00 rows=5000 loops=1)
Filter: (created_at > '2026-01-01'::date)
Rows Removed by Filter: 995000
-> Hash (cost=500.00..500.00 rows=1000 width=20) (actual time=10.00..10.00 rows=1000 loops=1)
-> Seq Scan on users u (cost=0.00..500.00 rows=1000 width=20) (actual time=0.02..5.00 rows=1000 loops=1)
Filter: (status = 'active'::text)
Пояснение: Seq Scan по orders удалил 995000 строк — это узкое место. Нужен индекс по created_at.
3. Создание индекса для ускорения фильтрации
Задача: Ускорить запросы с фильтром по дате.
Промт:
CREATE INDEX idx_orders_created_at ON orders (created_at);
-- После создания повторный EXPLAIN ANALYZE покажет Index Scan
Пример результата: Время выполнения снизилось с 2 с до 50 мс.
Пояснение: B-tree индекс — стандарт для сравнений. Для больших таблиц используйте CREATE INDEX CONCURRENTLY, чтобы избежать блокировок.
4. Поиск неиспользуемых индексов
Задача: Выявить индексы, которые только замедляют вставку.
Промт:
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexname NOT LIKE '%_pkey';
Пример результата:
| schemaname | tablename | indexname | idx_scan |
|---|---|---|---|
| public | orders | idx_orders_status | 0 |
| Пояснение: idx_scan = 0 означает, что индекс не использовался с момента сброса статистики. Такие индексы можно удалить. |
Продвинутые промты
5. Партиционирование большой таблицы по диапазону дат
Задача: Разбить таблицу orders на партиции по месяцам.
Промт:
-- Переименовываем исходную таблицу
ALTER TABLE orders RENAME TO orders_old;
-- Создаём партиционированную таблицу
CREATE TABLE orders (LIKE orders_old INCLUDING ALL) PARTITION BY RANGE (created_at);
-- Создаём партиции на 2026 год
CREATE TABLE orders_2026_01 PARTITION OF orders FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE orders_2026_02 PARTITION OF orders FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
-- Переносим данные
INSERT INTO orders SELECT * FROM orders_old;
Пример результата: Запросы с фильтром по месяцу теперь сканируют только одну партицию (partition pruning).
Пояснение: Партиционирование появилось в PostgreSQL 10 и значительно улучшилось в 11+. Официальная документация: https://www.postgresql.org/docs/current/ddl-partitioning.html.
6. Использование частичных индексов для горячих данных
Задача: Ускорить запросы только по активным заказам.
Промт:
CREATE INDEX idx_orders_active ON orders (user_id) WHERE status = 'active';
Пример результата: Индекс занимает в 5 раз меньше места и ускоряет выборку активных заказов в 3 раза.
Пояснение: Частичные индексы эффективны, когда запросы часто фильтруют по определённому условию.
7. Оптимизация соединений с помощью CTE и материализации
Задача: Ускорить сложный запрос с множеством JOIN.
Промт:
WITH active_users AS MATERIALIZED (
SELECT id FROM users WHERE status = 'active'
)
SELECT o.id, o.total
FROM orders o
JOIN active_users u ON o.user_id = u.id
WHERE o.created_at > '2026-01-01';
Пример результата: Время выполнения сократилось с 3 с до 1.2 с за счёт однократного вычисления active_users.
Пояснение: MATERIALIZED заставляет CTE выполняться один раз, а не инлайниться. Доступно с PostgreSQL 12.
8. Настройка autovacuum для больших таблиц
Задача: Предотвратить раздувание таблиц и замедление запросов.
Промт:
ALTER TABLE orders SET (autovacuum_vacuum_scale_factor = 0.01, autovacuum_analyze_scale_factor = 0.005);
Пример результата: Autovacuum стал запускаться чаще, bloat снизился на 30%.
Пояснение: По умолчанию scale_factor = 0.2, что для больших таблиц означает редкий запуск. Уменьшение параметра ускоряет очистку.
Экспертные промты
9. Горизонтальное шардирование с Citus
Задача: Распределить данные по нескольким узлам.
Промт:
-- Установка расширения Citus
CREATE EXTENSION citus;
-- Добавление рабочих узлов
SELECT citus_add_node('worker1', 5432);
-- Шардирование таблицы
SELECT create_distributed_table('orders', 'user_id');
Пример результата: Запросы к orders распределяются по 4 узлам, пропускная способность выросла в 3 раза.
Пояснение: Citus — официальное расширение для шардирования (https://www.citusdata.com/). Требует PostgreSQL 14+.
10. Мониторинг блокировок и мёртвых блокировок
Задача: Найти и устранить блокировки, вызывающие таймауты.
Промт:
SELECT blocked_locks.pid AS blocked_pid,
blocking_locks.pid AS blocking_pid,
blocked_activity.query AS blocked_query
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;
Пример результата: Выявлена блокировка от долгой транзакции, после завершения которой таймауты прекратились.
Пояснение: Запрос из официальной документации (https://www.postgresql.org/docs/current/monitoring-locks.html).
11. Использование BRIN-индексов для больших таблиц с естественной сортировкой
Задача: Ускорить запросы по диапазону в таблице с миллиардом строк.
Промт:
CREATE INDEX idx_orders_brin ON orders USING BRIN (created_at);
Пример результата: Индекс занял 1 МБ вместо 10 ГБ для B-tree, запросы по диапазону ускорились в 2 раза.
Пояснение: BRIN эффективен для данных, физически упорядоченных по колонке (например, время вставки). Документация: https://www.postgresql.org/docs/current/brin-intro.html.
Выводы
Оптимизация PostgreSQL — это непрерывный процесс. Начните с базовых промтов: анализа pg_stat_statements и EXPLAIN ANALYZE. Затем переходите к партиционированию и частичным индексам. Для высоконагруженных систем рассмотрите шардирование через Citus и BRIN-индексы. Помните, что каждый промт нужно адаптировать под вашу схему и нагрузку. Экспериментируйте на тестовой среде, измеряйте результат до и после. Если вы хотите глубже изучить эти техники, обратите внимание на наши курсы по PostgreSQL — там мы разбираем реальные кейсы и даём готовые шаблоны. Делитесь своими находками в комментариях!
Комментарии