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 раз, сначала разберите кардинальность. Новый индекс может быть полезен, но он не заменяет понимание связи между колонками и актуальности статистики.
🔹🔹🔹🔹