Индексы и EXPLAIN — главные инструменты для отладки производительности в PostgreSQL. Планировщик выбирает путь выполнения запроса на основе статистики, а EXPLAIN показывает, насколько удачным был этот выбор.
Возьмем типичный запрос поиска заказов за последний месяц с джойном на таблицу пользователей:
EXPLAIN ANALYZE
SELECT o.id, o.created_at, u.email
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.created_at >= CURRENT_DATE - INTERVAL '30 days'
AND o.status = 'completed';
-> Hash Join (cost=19.15..729.45 rows=238 width=80) (actual time=0.432..5.512 rows=245)
Hash Cond: (o.user_id = u.id)
-> Seq Scan on orders o (cost=0.00..685.20 rows=238 width=56)
Filter: (status = 'completed') AND (created_at >= (CURRENT_DATE - '30 days'::interval))
-> Hash (cost=12.40..12.40 rows=540 width=36)
-> Seq Scan on users u (cost=0.00..12.40 rows=540 width=36)
Sequential Scan в плане запроса — первый звоночек. База читает всю таблицу orders, чтобы найти записи за последний месяц. Создадим индекс по дате создания и статусу:
CREATE INDEX idx_orders_date_status ON orders(created_at, status);
EXPLAIN ANALYZE SELECT ...
-> Hash Join (cost=19.15..129.45 rows=238 width=80) (actual time=0.132..0.512 rows=245)
Hash Cond: (o.user_id = u.id)
-> Index Scan using idx_orders_date_status on orders o (cost=0.00..85.20 rows=238 width=56)
Filter: (status = 'completed') AND (created_at >= (CURRENT_DATE - '30 days'::interval))
-> Hash (cost=12.40..12.40 rows=540 width=36)
-> Seq Scan on users u (cost=0.00..12.40 rows=540 width=36)
Index Scan и время выполнения упало в 10 раз — именно то, что нужно. Но обратите внимание на Seq Scan для таблицы users. База все еще читает ее целиком, хотя мы достаем только email. Самое время для покрывающего индекса:
CREATE INDEX idx_users_email ON users(id) INCLUDE (email);
EXPLAIN не просто показывает план, но и помогает его улучшить. Главное — понимать, что стоит за каждой строчкой его вывода.
🏴☠️ @happy_devops