11 промтов для разгона PostgreSQL в Docker: EXPLAIN ANALYZE, индексы и партиционирование больших таблиц

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 — там мы разбираем реальные кейсы и даём готовые шаблоны. Делитесь своими находками в комментариях!

← Все статьи

Комментарии

Читайте также

Как автоматизировать A/B-тестирование целевых страниц с помощью AI-агентов и GPT-воронок: пошаговое руководство

21 июня 2026

Испанский с ИИ: почему персонализированное изучение языка превосходит традиционные занятия (гид 2026 года)

1 июля 2026

Курс «3D-моделирование в Blender»: обучение с AI на ASI Biont — ваш путь в мир 3D

17 июня 2026

Как автоматизировать международные платежи с Wise и AI-агентом ASI Biont: пошаговое руководство без кода

16 июля 2026

12 промтов для мультимодального AI: как автоматизировать анализ изображений, текста и видео в Excel

29 августа 2026

Охрана труда обучение: как AI помогает избежать штрафов ГИТ и автоматизировать СОУТ

5 июля 2026

От полигона участка до токов КЗ: как создать генератор подстанций на основе реального опыта

18 июля 2026

Как подключить промышленный контроллер (PLC) по Modbus/TCP к AI-агенту ASI Biont и управлять им через Telegram

30 июля 2026

Rust для Web (Actix/WASM): как создавать безопасные и молниеносные веб-сервисы — обучение на Asibiont

26 июня 2026