Промты для PostgreSQL: как ускорять запросы, чинить джойны и находить узкие места в проде
Медленный запрос в проде — это не просто красный график на дашборде. Это упавший SLA, ночной инцидент и вопрос от бизнеса «почему отчёт грузится 40 секунд». По моему опыту, большинство таких проблем решается не героическим переписыванием кода с нуля, а точной диагностикой: понять план выполнения, найти узкое место, изменить один индекс или переписать джойн.
Именно здесь LLM становятся полезным инструментом. Они не заменяют EXPLAIN ANALYZE и не знают вашу схему, но отлично работают как «второй инженер»: разбирают план запроса, предлагают варианты индексов, переписывают коррелированные подзапросы в CTE и находят N+1 в ORM-коде. Ключ — давать им правильный контекст: DDL таблиц, план выполнения, реальные метрики, версию PostgreSQL.
Ниже — 12 готовых промтов, которые я использую в работе с PostgreSQL. Каждый можно копировать как есть, подставляя свои таблицы и планы. Все примеры синтаксиса проверены на PostgreSQL 14–17.
1. Разбор плана EXPLAIN (ANALYZE, BUFFERS)
Когда запрос тормозит, первым делом нужен план. Промт заставляет модель читать его как инженер, а не как переводчик.
Промт:
Ты — эксперт по PostgreSQL. Ниже план EXPLAIN (ANALYZE, BUFFERS) для запроса.
Найди узкие места: самые дорогие узлы, несоответствие estimated rows и actual rows,
лишние Seq Scan, внешние сортировки и дисковые чтения.
Для каждой проблемы предложи конкретное действие (индекс, переписывание, настройка).
Версия PostgreSQL: 16. DDL таблиц: <вставь CREATE TABLE>.
План: <вставь вывод EXPLAIN>
Пример: на таблице orders с 12 млн строк планировщик оценил rows=1, а фактически вернул 240 000 — классический признак устаревшей статистики. Промт подскажет ANALYZE orders и, если нужно, повышение default_statistics_target для проблемной колонки.
2. Генерация индекса под конкретный запрос
Индексы — самый дешёвый способ ускорить чтение, но «индекс на всё» убивает запись. Промт помогает выбрать тип и порядок колонок.
Промт:
Вот запрос и DDL таблиц. Предложи оптимальный индекс: тип (btree, gin, gist, brin),
порядок колонок, partial или covering (INCLUDE).
Объясни, почему именно такой порядок, и оцени влияние на INSERT/UPDATE.
Учти селективность колонок и существующие индексы: <список>.
Пример: для WHERE tenant_id = $1 AND status = 'active' ORDER BY created_at DESC модель предложит составной btree (tenant_id, status, created_at DESC), а не три отдельных индекса.
3. Переписывание коррелированного подзапроса в JOIN
Коррелированные подзапросы в SELECT часто выполняются построчно. Промт переписывает их в LEFT JOIN LATERAL или агрегатный CTE.
Промт:
Перепиши запрос так, чтобы убрать коррелированный подзапрос.
Покажи 2 варианта: через LEFT JOIN LATERAL и через CTE с GROUP BY.
Для каждого варианта объясни план и когда он выигрывает.
Сохрани исходную семантику, включая NULL и дубликаты.
Пример: подзапрос (SELECT max(paid_at) FROM payments p WHERE p.order_id = o.id) превращается в LEFT JOIN LATERAL (SELECT max(paid_at) FROM payments p WHERE p.order_id = o.id) x ON true — планировщик делает один проход вместо N.
4. Диагностика N+1 в ORM-коде
N+1 не виден в SQL-логе, если смотреть по одному запросу. Промт анализирует дамп запросов и находит паттерн.
Промт:
Ниже лог SQL-запросов за один HTTP-запрос (pg_stat_statements или лог ORM).
Найди паттерн N+1: повторяющиеся запросы с разными параметрами.
Предложи исправление на уровне ORM (selectinload / include / prefetch) и на уровне SQL.
Пример: 1 запрос на список заказов + 300 запросов на клиента — модель укажет на selectinload(Order.customer) и предложит один WHERE customer_id = ANY($1).
5. Оптимизация оконных функций
Оконные функции красивы, но сортировка на миллионах строк дорога. Промт ищет, где можно заменить ROW_NUMBER на DISTINCT ON или материализовать промежуточный результат.
Промт:
Вот запрос с оконными функциями. Найди дорогие WindowAgg.
Предложи альтернативы: DISTINCT ON, LATERAL, агрегатный CTE.
Укажи, какие индексы поддержат ORDER BY внутри OVER().
Пример: ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) для «последней записи на пользователя» часто быстрее заменить на DISTINCT ON (user_id) ... ORDER BY user_id, created_at DESC с индексом (user_id, created_at DESC).
6. Поиск блокировок и долгих транзакций
Прод встал, а причина — одна транзакция, держащая lock. Промт помогает читать pg_locks и pg_stat_activity.
Промт:
Вот вывод pg_stat_activity и pg_locks. Найди блокирующую цепочку:
кто кого ждёт, какой объект залочен, сколько длится транзакция.
Предложи безопасный порядок действий и как предотвратить в будущем.
Пример: idle in transaction на 25 минут держит AccessExclusiveLock на таблице — модель подскажет statement_timeout и idle_in_transaction_session_timeout.
7. Рефакторинг сложного JOIN в CTE
Запрос с пятью джойнами нечитаем и плохо планируется. Промт разбивает его на именованные CTE с понятной семантикой.
Промт:
Разбей этот запрос на CTE по смыслу (фильтрация, агрегация, обогащение).
Сохрани порядок применения фильтров для производительности.
Проверь, не materialize лишнего: укажи, где нужен MATERIALIZED, а где нет.
Пример: в PostgreSQL 12+ CTE по умолчанию инлайнятся; модель подскажет WITH x AS MATERIALIZED (...) только там, где это реально выгодно.
8. Оптимизация пагинации (OFFSET → keyset)
OFFSET 100000 читает и выбрасывает 100 000 строк. Промт переводит пагинацию на keyset.
Промт:
Перепиши пагинацию с OFFSET/LIMIT на keyset (cursor-based) с сохранением сортировки.
Покажи SQL и как передавать курсор из приложения. Учти составной ORDER BY.
Пример: ORDER BY created_at DESC, id DESC LIMIT 20 + WHERE (created_at, id) < ($1, $2) — стабильно быстро на любом «номере страницы».
9. Чтение pg_stat_statements и поиск топ-запросов
Прежде чем оптимизировать, надо найти что оптимизировать. Промт превращает сырой дамп в приоритизированный список.
Промт:
Вот top-30 из pg_stat_statements по total_exec_time.
Отсортируй по влиянию на общую нагрузку, а не только по среднему времени.
Для каждого предложи гипотезу проблемы и следующий шаг диагностики.
Пример: запрос с calls=2 и mean_time=8s может быть важнее, чем calls=500000 с mean_time=2ms — промт объяснит разницу между total и mean.
10. Безопасная миграция схемы без блокировок
ALTER TABLE ... ADD COLUMN DEFAULT в старых версиях лочил таблицу. Промт проверяет, безопасна ли миграция.
Промт:
Проверь миграцию на блокировки в PostgreSQL 16.
Какие шаги требуют AccessExclusiveLock и на сколько?
Предложи безопасный порядок: NOT VALID constraints, CONCURRENTLY, батчи.
Пример: CREATE INDEX CONCURRENTLY вместо CREATE INDEX, ADD CONSTRAINT ... NOT VALID + отдельный VALIDATE CONSTRAINT.
11. Генерация тестовых данных для нагрузочного теста
Чтобы проверить индекс, нужны реалистичные данные. Промт пишет генератор.
Промт:
Напиши SQL для генерации N строк в таблицу с реалистичным распределением
(например, 80% заказов в 20% клиентов). Используй generate_series и random().
Добавь индексы после загрузки, чтобы ускорить вставку.
Пример: INSERT INTO orders SELECT ... FROM generate_series(1, 1000000) с setseed() для воспроизводимости.
12. Объяснение запроса для код-ревью
Последний, но не по важности: промт-«ревьюер», который объясняет сложный SQL коллегам и ловит логические ошибки.
Промт:
Объясни этот запрос по шагам человеческим языком: что фильтруется, что джойнится,
что агрегируется. Укажи потенциальные баги: неявные CAST, NULL в NOT IN,
дубликаты из-за JOIN, потерянные строки из-за INNER JOIN.
Пример: NOT IN (SELECT ...) с NULL в подзапросе вернёт пустой результат — классическая ловушка, которую промт подсветит.
Как использовать это на практике
Главное правило: модель не знает вашу схему и статистику. Чем больше контекста вы даёте — DDL, план, версия, объём данных — тем полезнее ответ. Не доверяйте советам слепо: любой предложенный индекс проверяйте через EXPLAIN (ANALYZE, BUFFERS) до и после, а миграции — на копии прода.
Официальные источники, на которые стоит опираться: документация PostgreSQL по EXPLAIN, индексам и pg_stat_statements. Если хотите системно прокачать работу с базами и ИИ-инструментами — посмотрите материалы и практикумы на asibiont.com: там разбираются реальные сценарии, а не абстрактная теория.
Комментарии