Частичные индексы в PostgreSQL

В таблице заказов миллионы строк, но интересуют нас обычно только «зависшие» — те, что в статусе pending. Обычный индекс по (status, created_at) справляется, но он большой и обновляется при каждой вставке. Частичный индекс хранит только нужные строки:

CREATE INDEX CONCURRENTLY orders_pending_created_idx
    ON orders (created_at)
    WHERE status = 'pending';

Запрос, который им воспользуется:

SELECT id, created_at
FROM orders
WHERE status = 'pending'
  AND created_at < now() - interval '1 hour'
ORDER BY created_at
LIMIT 100;

Что важно помнить

  • Планировщик берёт частичный индекс, только если может доказать, что условие запроса влечёт условие индекса. status = 'pending' в тексте запроса — да; status = $1 в общем (generic) плане — нет, значение параметра там неизвестно.
  • С prepared statements PostgreSQL первые несколько выполнений строит отдельные планы под конкретные значения, а потом может перейти на общий — и индекс внезапно «пропадёт». Помогает литерал в запросе или SET plan_cache_mode = force_custom_plan для этого места.
  • CREATE INDEX CONCURRENTLY не блокирует запись в таблицу, но его нельзя выполнять внутри транзакции, а после ошибки остаётся невалидный индекс — его нужно удалить и создать заново.

Проверить, что индекс действительно используется: EXPLAIN (ANALYZE, BUFFERS) на реальном запросе и счётчик idx_scan в pg_stat_user_indexes.