awesome-everything EN
↑ Обратно к восхождению

Базы данных

Алгоритмы соединения и каскад ошибок оценки строк

Суть Postgres выбирает Nested Loop, Hash Join или Merge Join на основе оценок строк. Когда внешняя сторона недооценена в 1000×, Nested Loop взрывается — алгоритм является симптомом, а плохая оценка строк — причиной.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 16 min

Запрос возвращает 50 строк за 50 мс на staging. В production — та же схема, идентичные индексы, тот же запрос — 4.2 секунды. EXPLAIN ANALYZE показывает Nested Loop ... loops=520000. Внутренний index scan выполнился полмиллиона раз. Планировщик думал, что на внешней стороне 50 строк. Их было 520,000. Одна плохая оценка. Один неверный выбор соединения. Замедление в 80×.

Три алгоритма соединения

АлгоритмФорма стоимостиПобеждает когдаОпасность
Nested Loopouter_rows × inner_costВнешняя сторона мала (десятки строк), внутренняя имеет индексВзрывается при недооценке внешней стороны как малой
Hash Joinbuild_cost + probe_costСредние и большие equi-join; ни одна сторона не имеет индекса на ключе сортировкиСбрасывается на диск, если хеш-таблица превышает work_mem
Merge Joinsort_cost + merge_costОбе стороны уже отсортированы (подходящий индекс); большие equi-joinТребует отсортированного ввода; сортировка может сброситься при малом work_mem

Nested Loop

Для каждой строки внешнего отношения ищет совпадающие строки во внутреннем — как правило, через индекс. Стоимость: outer_rows × inner_cost_per_lookup. Побеждает, когда внешняя сторона мала (десятки строк), потому что стоимость внутреннего поиска по индексу уплачивается столько же раз. Катастрофически проигрывает при недооценке внешней стороны: если планировщик думает, что внешних строк 10, а их 10,000, внутренний поиск выполняется 10,000 раз вместо 10 — в 1000× больше работы.

Диагностика: счётчик loops на внутреннем узле. В здоровом Nested Loop: loops=50. В «взорвавшемся»: loops=520000.

Hash Join

Строит хеш-таблицу из меньшей (build) стороны, затем проверяет её строками из большей (probe) стороны. Стоимость: build + probe. Побеждает для средних и больших equi-join, где ни одна сторона не имеет индекса, выровненного по ключу соединения. Критический параметр — work_mem: хеш-таблица должна помещаться в памяти. Когда нет, Hash Batches превышает 1 и таблица сбрасывается на диск — ищите Batches: 64 или аналогичное в выводе плана. Исправление: SET work_mem = '64MB' для сессии (не глобально, без учёта max_connections).

Merge Join

Сортирует обе стороны по ключу соединения (или использует индексы, уже обеспечивающие порядок), затем сливает параллельно. Побеждает, когда обе стороны приходят отсортированными — например, при соединении двух таблиц с ORDER BY id и соответствующими индексами. Не добавляет дополнительной стоимости сортировки. Полезен для range join и когда порядок сортировки нужен и для финального результата.

Каскад ошибок оценки строк

Это самая важная концепция урока. Плохая оценка строк на одном узле плана каскадирует на каждый узел выше:

  1. Планировщик думает, что фильтр WHERE country='US' AND region='CA' AND status='shipped' вернёт 50 строк (независимые вероятности: 50% × 5% × 20% = 0.5%)
  2. Планировщик выбирает Nested Loop — дёшево когда внешняя сторона мала
  3. Реальность: колонки коррелированы (все CA-заказы в US), реальная селективность 5% × 20% = 1% — и планировщик ошибся с кардинальностью индекса; реальных строк = 520,000
  4. Внутренний index scan выполняется 520,000 раз вместо 50 — в 10,400× больше работы

Неверный алгоритм (Nested Loop вместо Hash Join) — симптом. Неверная оценка строк — причина. Принудительный выбор алгоритма (например, SET enable_nestloop = off) маскирует симптом, оставляя корень причины. Исправьте оценку — выбор алгоритма последует.

Несаргабельные предикаты

Предикат «саргабелен» (Search ARGument-ABLE), если планировщик может использовать индекс для его вычисления. Несаргабельные предикаты принудительно вызывают Seq Scan и искажают оценки строк.

Типичные нарушители:

  • WHERE LOWER(email) = 'alice@x.com' — функция на индексируемой колонке → используйте выражаемый индекс CREATE INDEX ON users (LOWER(email))
  • WHERE created_at::date = '2026-01-01' — приведение типа → перепишите как WHERE created_at >= '2026-01-01' AND created_at < '2026-01-02'
  • WHERE EXTRACT(year FROM created_at) = 2026 — вызов функции → тот же диапазонный рефактор
  • WHERE id::text = '42' — неявное приведение → WHERE id = 42 (исправьте тип в приложении)
  • WHERE name LIKE '%foo' — лидирующий wildcard → GIN-индекс pg_trgm для нечёткого поиска

EXPLAIN сразу это обнаруживает: Seq Scan + Filter там, где ожидался Index Scan. Строка Filter показывает, что применялось после сканирования, а не до — то есть индекс не помог.

Диагностика и исправление взрыва оценки строк

1/3
Викторина

EXPLAIN ANALYZE показывает `Nested Loop (cost=0..50 rows=10) ... -> Index Scan ... (loops=10000)`. Какова наиболее вероятная причина?

Викторина

В плане запроса показано `Hash Join ... Hash Batches: 64`. Что это означает и каково исправление?

Викторина

Какой предикат является НЕСАРГАБЕЛЬНЫМ и вызовет последовательное сканирование, даже если на `created_at` есть индекс?

Вспомните перед уходом
  1. 01
    Объясните каскад ошибок оценки строк: почему плохая оценка на одном узле ломает весь план выше него?
  2. 02
    Когда Hash Join является правильным выбором и что заставляет его сбрасываться на диск?
  3. 03
    Что такое несаргабельный предикат, почему он важен для производительности и как исправить типичные случаи?
Итог

Postgres выбирает среди трёх алгоритмов соединения: Nested Loop (outer_rows × inner_cost, побеждает при малых внешних с внутренним индексом), Hash Join (построить хеш-таблицу + проверить, побеждает для средних и больших equi-join, сбрасывается на диск когда хеш-таблица превышает work_mem), Merge Join (сортировать обе стороны, слить параллельно, побеждает когда обе стороны приходят отсортированными). Выбор алгоритма определяется оценками строк на каждом узле — недооценка в 1000× на внешней стороне Nested Loop превращает внутренний поиск в 1000× больше работы, чем запланировано. Неверный алгоритм всегда симптом; неверная оценка строк всегда причина. Исправляйте оценки (ANALYZE, расширенная статистика) и выбор алгоритма скорректируется сам. Несаргабельные предикаты (функции на индексируемых колонках, неявные приведения) предотвращают использование индекса и искажают оценки — перепишите их как диапазонные предикаты или добавьте выражаемые индексы.

Практика

Сделай это, чтобы превратить узнавание в навык.

Связанные уроки
встречается в174
Продолжить восхождение ↑pg_statistic, ANALYZE и производственная наблюдаемость
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.