Урок по треку SQL и базы данных 8 мин

EXPLAIN и оптимизация за 8 минут

Чтение плана, виды сканов и соединений, статистика, N+1 и пагинация.

Запрос тормозит. Не гадайте, смотрите план.

EXPLAIN и EXPLAIN ANALYZE

  • EXPLAIN SELECT ... показывает план и оценки планировщика, запрос не выполняется.
  • EXPLAIN ANALYZE SELECT ... выполняет запрос и показывает фактическое время и число строк. Осторожно с UPDATE и DELETE: они реально изменят данные (оборачивайте в BEGIN ... ROLLBACK).
  • EXPLAIN (ANALYZE, BUFFERS) добавляет, сколько страниц прочитано из кеша (shared hit) и с диска (read).
Index Scan using orders_customer_id_idx on orders
  (cost=0.43..8.45 rows=3 width=40) (actual time=0.020..0.025 rows=3 loops=1)
  Index Cond: (customer_id = 42)
  • cost=0.43..8.45: стоимость до первой строки и до последней, в условных единицах.
  • rows=3: оценка планировщика. Рядом actual rows: факт.
  • loops: сколько раз выполнялся узел. Время и строки указаны на один проход.

План читается изнутри наружу: самые вложенные узлы выполняются первыми.

Виды чтения

  • Seq Scan: читает всю таблицу. Нормально для маленьких таблиц и когда нужна большая доля строк.
  • Index Scan: идёт по индексу и за каждой строкой в таблицу.
  • Index Only Scan: всё нужное в индексе, в таблицу почти не ходит.
  • Bitmap Index Scan + Bitmap Heap Scan: собирает из индекса битовую карту страниц, потом читает страницы по порядку. Хорош для «средней» селективности и для комбинации нескольких индексов.

Виды соединений

  • Nested Loop: для каждой строки внешней стороны ищет пару во внутренней. Отлично, когда внешних строк мало, а внутри есть индекс.
  • Hash Join: строит хеш-таблицу по меньшей стороне, потом проходит большую. Хорош для больших наборов и равенства.
  • Merge Join: обе стороны отсортированы по ключу, идёт «молнией».

Главный сигнал: оценка против факта

Если rows=10, а actual rows=500000, планировщик ошибся и выбрал плохой план (например, Nested Loop на полмиллиона строк). Причины: устаревшая статистика, коррелирующие колонки, сложные выражения. Лечение: ANALYZE таблица, увеличить default_statistics_target для колонки, расширенная статистика CREATE STATISTICS для зависимых колонок.

Типовые проблемы и решения

  • Нет индекса под условие или JOIN: добавить, учитывая порядок колонок.
  • Функция над колонкой в WHERE: индекс по выражению или переписать условие (created_at >= '2026-01-01' AND created_at < '2026-01-02' вместо date(created_at) = ...).
  • Сортировка на диске: в плане Sort Method: external merge Disk. Помогает индекс под ORDER BY или больше work_mem.
  • Большой OFFSET: keyset-пагинация WHERE (created_at, id) < ($1, $2) ORDER BY created_at DESC, id DESC LIMIT 20.
  • SELECT *: тянет лишние колонки, мешает Index Only Scan.
  • N+1: приложение делает 1 запрос за списком и N запросов за деталями. Лечится JOIN, WHERE id = ANY($1) или пакетной загрузкой в ORM.
  • Раздутая таблица после массовых UPDATE и DELETE: проверить autovacuum.

Наблюдение

  • pg_stat_statements: какие запросы суммарно съедают больше всего времени. Оптимизируют их, а не самый медленный одиночный.
  • log_min_duration_statement: писать в лог запросы дольше N мс.
  • auto_explain: автоматически логировать планы медленных запросов.

Частые ошибки

  • Смотреть только EXPLAIN без ANALYZE и верить оценкам.
  • Запустить EXPLAIN ANALYZE DELETE на проде.
  • Считать любой Seq Scan проблемой.
  • Оптимизировать запрос, который выполняется раз в день, вместо того, что вызывается 1000 раз в секунду.

Что спросят на собесе

  • Как найти и разобрать медленный запрос.
  • Чем EXPLAIN отличается от EXPLAIN ANALYZE.
  • Какие бывают сканы и соединения, когда какой хорош.
  • Что такое проблема N+1 и как её лечить.
  • Почему планировщик выбрал seq scan, хотя индекс есть.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.

Вопросы модуля (37)