Статистика планировщика PostgreSQL: разбор инцидента
После загрузки данных PostgreSQL может выбрать другой план выполнения: вместо точечного поиска — полный проход по таблице, а запросы замедлятся. Разбираем, как отличить устаревшую статистику от ошибки оценки и что проверить на PostgreSQL 16.

После массовой загрузки данных запрос, который раньше быстро находил несколько строк, может начать читать всю таблицу. В типовом инциденте приложение при этом не менялось: PostgreSQL неверно оценил число подходящих строк и выбрал дорогой план. Разберём, как подтвердить эту причину на PostgreSQL 16, обновить статистику и не лечить вслепую не связанную с ней проблему.
Что именно ломается
Планировщик выбирает последовательность действий по оценкам: сколько строк пройдёт через фильтр, сколько страниц придётся прочитать и во сколько обойдутся разные способы соединения таблиц. Оценки строятся в том числе на статистике, которую собирает команда ANALYZE. Если данные изменились, а статистика не отражает новое распределение, дешёвый по расчёту план может оказаться медленным в реальности.
Показательный сценарий — таблица заказов, где большая часть строк имеет статус archived, а новые строки массово добавили со статусом paid. Статистика могла не успеть за загрузкой или выборка при её сборе могла недостаточно точно описать перекос. Запрос к paid начинает обрабатывать гораздо больше строк, чем ожидал планировщик. Это не отчёт о конкретной компании: сценарий нужен, чтобы показать порядок диагностики без выдуманных замеров.
Термин. Селективность — доля строк, которую условие оставляет после фильтра. Чем она ниже, тем меньше строк проходит дальше; неверная оценка этой доли может изменить выбранный план.
Не сравнивайте только время запроса. Главная улика — расхождение между числом строк, которое планировщик ожидал, и числом, которое узел действительно обработал.
Сначала сопоставьте план с реальностью
Для PostgreSQL 16 выполните запрос с теми же условиями и параметрами, на которых проявляется замедление. Ниже предполагается, что в public.orders есть столбцы status и customer_id; замените их на поля своей схемы.
EXPLAIN (ANALYZE, BUFFERS, SETTINGS)
SELECT *
FROM public.orders
WHERE status = 'paid'
AND customer_id = 42;
ANALYZE внутри команды запускает запрос, поэтому выводит фактические строки и время. Не запускайте такой запрос без оценки риска на тяжёлой операции: он выполняет её, а не просто строит план. Сравните в узлах дерева rows и actual rows, а также число циклов в loops. При нескольких циклах оценку строк одного выполнения сопоставляйте с фактическим количеством одного цикла, а не механически с суммой по всему узлу.
Затем проверьте статистику отдельных столбцов:
SELECT attname, null_frac, n_distinct,
most_common_vals, most_common_freqs, histogram_bounds
FROM pg_stats
WHERE schemaname = 'public'
AND tablename = 'orders'
AND attname IN ('status', 'customer_id');
Если у плана ожидаются единицы строк, а фактически проходят тысячи, это заметное расхождение. Если оценки близки, причина может быть в стоимости чтения, блокировках, конкурирующей нагрузке или другой части плана. Сам факт полного сканирования не доказывает ошибку: для запроса, который читает значительную долю таблицы, последовательное чтение может быть разумным выбором.
Проверьте статистику и исправьте причину
- Убедитесь, что данные изменились. Сопоставьте время загрузки с последним сбором статистики и проверьте, охватывает ли запрос ожидаемые партиции. Это помогает не менять настройки, когда проблема в другом объекте или в другом диапазоне данных.
- Обновите статистику таблицы. Выполните
ANALYZE public.orders;. Команда читает выборку строк, поэтому на большой таблице может добавить нагрузку на диск и процессор, хотя обычные чтение и запись она не заменяет и не отключает. После выполнения повторите исходныйEXPLAIN (ANALYZE, BUFFERS): ожидается, что оценка строк станет ближе к фактической, если причина была в устаревшей статистике. - Если промах связан с одним перекошенным столбцом, увеличьте детализацию для него. Например,
ALTER TABLE public.orders ALTER COLUMN status SET STATISTICS 500;, затем снова выполнитеANALYZE public.orders;. Значение статистической цели здесь не универсальный рецепт: более детальная статистика может точнее описывать редкие значения, но увеличивает объём работы при сборе и хранения статистики. Проверяйте результат на характерных запросах. - Если важна связь между столбцами, проверьте расширенную статистику. Например, статус и идентификатор клиента могут быть не независимы. Для PostgreSQL 16 можно собрать статистику наиболее частых сочетаний и зависимостей:
CREATE STATISTICS orders_status_customer_stats (dependencies, mcv) ON status, customer_id FROM public.orders;, затем выполнитьANALYZE public.orders;. В плане ищите улучшение оценки именно для условий, использующих эти столбцы вместе. - Повторите измерение в сопоставимых условиях. Сравните один и тот же запрос, параметры и состояние нагрузки до и после. Зафиксируйте фактические строки, буферы и время; так вы проверите, помогло ли изменение, а не просто совпало ли оно с кратковременным спадом нагрузки.
У расширенной статистики есть границы: она описывает столбцы одной таблицы и не является универсальным решением для оценки соединений. Если основная ошибка возникает на стыке таблиц, искать причину нужно отдельно. Не добавляйте несколько объектов статистики наугад: каждый из них расширяет работу при сборе и требует проверки конкретного запроса.
Какой способ выбрать
| Наблюдение | Что проверить | Цена и ограничение |
|---|---|---|
| Статистика старше загрузки или массового изменения | ANALYZE для таблицы | Потребляет ресурсы на сбор; не исправит неверную модель запроса |
| Оценка промахивается на одном столбце с редкими значениями | Поднять цель статистики для столбца | Больше данных для статистики и работы при её обновлении |
| Ошибку даёт сочетание полей одной таблицы | Расширенная статистика подходящего вида | Помогает не для всех выражений и не заменяет статистику соединений |
| Оценки строк близки, но запрос всё равно медленный | Буферы, фактические циклы, конкурирующая нагрузка | Причина может быть вне статистики; её обновление не гарантирует ускорение |
Где этот диагноз ломается
Свежая статистика не заставит индекс победить последовательное чтение, если запрос выбирает большую часть таблицы. Не поможет она и в случае, когда условие запроса отличается от того, которое вы проверяли, либо параметры в приложении ведут к иной оценке плана. Сравнивайте план именно для проблемного запроса и реальных значений.
Автоматический сбор статистики может не сработать вовремя после большой загрузки: настройка зависит от размера таблицы и накопившихся изменений. Поэтому после разовой массовой загрузки полезно проверить оценки вручную и при необходимости выполнить ANALYZE, не полагаясь только на фоновый механизм. Для постоянной проблемы смотрите, как часто меняются данные, а не просто запускайте команду по расписанию без проверки.
Наконец, не отключайте последовательное сканирование ради «проверки» и не считайте принудительный план доказательством правильного решения. Это может скрыть ошибку оценки, но не устранить её; на других значениях тот же план способен оказаться хуже.
Что делать дальше
После исправления добавьте к наблюдению за проблемным запросом не только время выполнения, но и расхождение оценочных и фактических строк. Сохраните план до и после, проверьте несколько характерных значений фильтра и убедитесь, что улучшение не ухудшило соседние запросы. Если оценки строк уже близки, переходите к анализу буферов, циклов и конкурирующей нагрузки: дальнейшее увеличение статистики в таком случае может лишь добавить стоимость обслуживания.
Смежная тема — настройка автоанализа для конкретной нагрузки; здесь разбирали диагностику одного инцидента, а не универсальную конфигурацию фоновой статистики.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: PostgreSQL 16: статистика планировщика, PostgreSQL 16: EXPLAIN
Частые вопросы
Нужно ли перезапускать PostgreSQL после ANALYZE?
Безопасно ли запускать ANALYZE на рабочей базе?
Если после ANALYZE запрос не ускорился, что делать?
Почему планировщик выбрал последовательное сканирование, хотя индекс есть?
Расширенная статистика исправит оценки для JOIN?
Можно ли повторить рекомендации на другой версии PostgreSQL?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.