Gymterview
middle

Как читать план выполнения запроса?

План выполнения — это дерево узлов (nodes). Выполнение идёт от внутренних узлов к внешним (снизу вверх). Чтение плана — ключевой навык для оптимизации SQL.

Основные типы узлов сканирования

Узел Описание
Seq Scan Последовательное чтение всей таблицы
Index Scan Чтение через индекс, затем обращение к таблице за данными
Index Only Scan Все нужные данные уже есть в индексе
Bitmap Index Scan + Bitmap Heap Scan Двухфазное: сначала собирается битовая карта по индексу, затем читаются страницы

Типы соединений (JOIN)

Узел Описание
Nested Loop Для каждой строки внешнего набора ищем во внутреннем. Хорош при малом внешнем наборе
Hash Join Строится хеш-таблица по одной стороне. Эффективен для больших наборов
Merge Join Оба набора сортируются, затем сливаются. Эффективен, если данные уже отсортированы
Пример чтения сложного плана
EXPLAIN ANALYZE
SELECT t.id, t.amount, c.name
FROM transactions t
JOIN clients c ON t.client_id = c.id
WHERE t.created_at > '2024-01-01'
  AND t.amount > 50000;
Hash Join  (cost=30.00..150.00 rows=100 width=48) (actual time=0.5..2.1 rows=87 loops=1)
  Hash Cond: (t.client_id = c.id)
  ->  Bitmap Heap Scan on transactions t  (cost=10.00..120.00 rows=100 width=24) (actual time=0.3..1.5 rows=87 loops=1)
        Recheck Cond: (created_at > '2024-01-01')
        Filter: (amount > 50000)
        Rows Removed by Filter: 213
        ->  Bitmap Index Scan on idx_tx_date  (cost=0.00..9.97 rows=300 width=0) (actual time=0.2..0.2 rows=300 loops=1)
              Index Cond: (created_at > '2024-01-01')
  ->  Hash  (cost=15.00..15.00 rows=500 width=32) (actual time=0.1..0.1 rows=500 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 40kB
        ->  Seq Scan on clients c  (cost=0.00..15.00 rows=500 width=32) (actual time=0.01..0.05 rows=500 loops=1)
Planning Time: 0.2 ms
Execution Time: 2.3 ms

На что обращать внимание

  • Большое расхождение между estimated и actual rows — устаревшая статистика, нужен ANALYZE
  • Seq Scan на большой таблице с фильтром — возможно, не хватает индекса
  • Rows Removed by Filter — строки прочитаны, но отброшены; признак неоптимального индекса
  • Buffers: shared read — чтение с диска (медленно), shared hit — чтение из кеша

На собеседовании: не нужно помнить все типы узлов. Покажите, что умеете читать план: снизу вверх, сравнивать estimated vs actual rows, искать Seq Scan на больших таблицах и «Rows Removed by Filter». Это три красных флага, которые решают 80% проблем производительности.