Найти в Дзене
Записки о Java

Как выполняется SQL-запрос в PostgreSQL: порядок операций, план выполнения и разбор 10 реальных примеров

Когда вы пишете SELECT * FROM users WHERE active = true, вы думаете, что PostgreSQL «просто выбирает строки». Но на самом деле запускается сложный конвейер из десятков этапов: парсинг, планирование, оптимизация, выполнение, управление транзакциями, MVCC, буферизация… Понимание этого процесса — ключ к написанию быстрых, предсказуемых и масштабируемых приложений. Особенно если вы используете Spring Data JPA или JDBC в Java 11. Давайте разберёмся по шагам, а затем проанализируем 10 самых частых запросов — как они работают под капотом. Многие думают, что SQL выполняется в порядке написания: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY.
Это неверно. Логический порядок выполнения (тот, который определяет, какие данные доступны на каком этапе): 💡 Это логический порядок. Физически PostgreSQL может выполнять этапы в другом порядке (благодаря оптимизатору), но результат будет эквивалентен этому порядку. Когда вы отправляете запрос через JDBC (или Hibernate), PostgreSQL проходит следующ
Оглавление

Когда вы пишете SELECT * FROM users WHERE active = true, вы думаете, что PostgreSQL «просто выбирает строки». Но на самом деле запускается сложный конвейер из десятков этапов: парсинг, планирование, оптимизация, выполнение, управление транзакциями, MVCC, буферизация…

Понимание этого процесса — ключ к написанию быстрых, предсказуемых и масштабируемых приложений. Особенно если вы используете Spring Data JPA или JDBC в Java 11.

Давайте разберёмся по шагам, а затем проанализируем 10 самых частых запросов — как они работают под капотом.

🔁 Логический порядок выполнения SQL-запроса (не путать с синтаксическим!)

Многие думают, что SQL выполняется в порядке написания: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY.
Это неверно.

Логический порядок выполнения (тот, который определяет, какие данные доступны на каком этапе):

  1. FROM — загрузка таблиц, применение JOIN
  2. WHERE — фильтрация строк
  3. GROUP BY — группировка
  4. HAVING — фильтрация групп
  5. SELECT — выборка столбцов, вычисление выражений
  6. DISTINCT — удаление дубликатов
  7. ORDER BY — сортировка
  8. LIMIT / OFFSET — ограничение результата
💡 Это логический порядок. Физически PostgreSQL может выполнять этапы в другом порядке (благодаря оптимизатору), но результат будет эквивалентен этому порядку.

⚙️ Физический жизненный цикл запроса в PostgreSQL

Когда вы отправляете запрос через JDBC (или Hibernate), PostgreSQL проходит следующие этапы:

1. Парсинг (Parsing)

  • Проверка синтаксиса.
  • Преобразование в parse tree.

2. Анализ (Analysis / Rewriting)

  • Проверка существования таблиц и колонок.
  • Применение правил (RULES).
  • Преобразование в query tree.

3. Планирование (Planning)

  • Оптимизатор генерирует план выполнения.
  • Использует статистику (pg_stats) для оценки стоимости.
  • Выбирает: использовать индекс или Seq Scan? Hash Join или Nested Loop?

4. Выполнение (Execution)

  • Запуск плана.
  • Чтение данных из буферного кэша или диска.
  • Применение MVCC: каждая транзакция видит свой «снимок» данных.
  • Возврат результата клиенту (вашему Java-приложению).
Важно для Java: все эти этапы происходят на стороне СУБД. Ваш код получает только результат.

🔍 Как посмотреть, что делает PostgreSQL?

Используйте EXPLAIN (ANALYZE, BUFFERS):

EXPLAIN (ANALYZE, BUFFERS)

SELECT u.name FROM users u WHERE u.email = 'test@example.com';

Это покажет:

  • Тип сканирования (Index Scan, Seq Scan),
  • Время выполнения,
  • Использование буферов,
  • Фактическое vs ожидаемое количество строк.

📊 Разбор популярных запросов: как они работают в PostgreSQL

Пример 1. Простой SELECT с WHERE

SELECT id, name FROM users WHERE email = 'a@b.com';

Как выполняется:

  1. FROM users — читает таблицу.
  2. WHERE email = ... — фильтрует.
  3. SELECT id, name — возвращает столбцы.

Оптимизация:

  • Если есть индекс по email → Index Scan.
  • Иначе → Seq Scan.
💡 Java-совет: всегда создавайте индекс по полям в WHERE, особенно если они используются в @Query.

Пример 2. JOIN двух таблиц

SELECT u.name, o.total

FROM users u

JOIN orders o ON u.id = o.user_id

WHERE u.active = true;

Порядок:

  1. FROM — соединяет users и orders.
  2. WHERE — фильтрует по u.active.
  3. SELECT — выбирает нужные поля.

Как PostgreSQL соединяет:

  • Nested Loop: если одна таблица мала.
  • Hash Join: если обе большие, но нет индекса.
  • Merge Join: если обе отсортированы.
💡 Оптимизация: индекс по orders(user_id) + users(active).

Пример 3. GROUP BY + агрегация

SELECT status, COUNT(*)

FROM orders

GROUP BY status;

Порядок:

  1. FROM orders
  2. GROUP BY status
  3. SELECT status, COUNT(*)

Как работает:

  • PostgreSQL строит хэш-таблицу по status.
  • Для каждой группы считает COUNT.
⚠️ Без индекса по status — будет HashAggregate после Seq Scan.

Пример 4. HAVING после GROUP BY

SELECT user_id, COUNT(*)

FROM orders

GROUP BY user_id

HAVING COUNT(*) > 5;

Порядок:

  1. Группировка по user_id
  2. Подсчёт COUNT(*)
  3. Фильтрация групп через HAVING
💡 Производительность: если таких пользователей мало — рассмотрите материализованное представление.

Пример 5. ORDER BY + LIMIT (пагинация)

SELECT id FROM users ORDER BY created_at DESC LIMIT 10;

Как выполняется:

  1. Читает все строки (Seq Scan или Index Scan).
  2. Сортирует по created_at DESC.
  3. Берёт первые 10.

Оптимизация:

  • Индекс по (created_at DESC) → Index Scan Backwardбез сортировки!
Best practice для Java: всегда используйте индекс по полю сортировки в пагинации.

Заключение

PostgreSQL — не «чёрный ящик». Он — сложная, но предсказуемая система, основанная на теории реляционных баз данных, MVCC и cost-based оптимизации.