rows=300, actual rows=420000: почему планировщик ошибся так сильно

Фрагмент фактического плана:

rows=300 actual rows=420000

Запрос фильтрует данные так:

status = 'paid' AND paid_at IS NOT NULL

Первая мысль: «Нужно добавить ещё один индекс».

Но сильное расхождение между rows и actual rows — отдельный диагностический сигнал. Проблема может быть не в том, что PostgreSQL «не нашёл индекс», а в том, что он неверно оценил, сколько строк пройдёт фильтр.

Почему так бывает? Колонки могут быть связаны. Например, status = 'paid' почти всегда означает, что paid_at заполнен. Для разработчика это понятная бизнес-логика, но планировщику нужна статистика.

Если обычной статистики по отдельным колонкам недостаточно для учёта связи, оценка может быть сильно занижена:

ожидание: 300 строк факт: 420000 строк

И дальше ошибка расходится по плану: соединение, сортировка, агрегация или nested loop выбираются из расчёта на один объём данных, а выполняются на другом.

Что проверить:

— насколько отличаются rows и actual rows в EXPLAIN ANALYZE; — актуальна ли статистика после изменений данных; — есть ли в фильтре связанные колонки; — поможет ли ANALYZE; — уместны ли extended statistics по набору колонок.

Важно: extended statistics — не «магическая кнопка» и не замена индексу. Это способ дать планировщику больше информации о данных, когда обычной статистики по отдельным колонкам недостаточно.

Практический вывод: если оценка отличается от факта больше чем в 1000 раз, сначала разберите кардинальность. Новый индекс может быть полезен, но он не заменяет понимание связи между колонками и актуальности статистики.

🔹🔹🔹🔹

rows=300, actual rows=420000: почему планировщик ошибся так сильно
Фрагмент фактического плана:
rows=300
actual rows=420000
Запрос фильтрует данные так:
status = 'paid'
AND paidat IS NOT NULL
Первая м... | Сетка — социальная сеть от hh.ru