Learning Platform
Урок 08.04 · 24 мин
Продвинутый
extended statisticscorrelationndistinctdependenciesmcv

В прошлом уроке мы видели, как Postgres оценивает AND между предикатами по разным колонкам: sel(A AND B) = sel(A) × sel(B). Эта формула предполагает статистическую независимость — что значение в одной колонке не несёт информации о другой. Иногда это правда. Часто — нет.

WHERE city = 'Москва' AND country = 'RU' — классика. Если городов 1000, а стран 50, то независимо: 1/1000 × 1/50 = 0.00002. На таблице в 1M строк — оценка 20 строк. Реально — 50 000 (все Москвы — это RU, и в Москве ~5% населения). Ошибка 2500x, план разваливается.

CREATE STATISTICS (с Postgres 10) — это способ сказать планировщику: «эти колонки связаны, посчитай для них совместную статистику».

Три типа extended statistics

Команда: CREATE STATISTICS name [(type [, ...])] ON col1, col2 [, ...] FROM table;

Типов три, и они независимы (можно один, можно все):

  1. ndistinct — число уникальных пар (или комбинаций) значений. Помогает с GROUP BY a, b и при оценке join.
  2. dependencies — функциональные зависимости между колонками. «Значение country определяется значением city». Используется для оценки WHERE с конъюнкциями.
  3. mcv — multi-column MCV. Список самых частых комбинаций значений с их частотами. Самый точный, но дороже всех.
Три типа extended statistics

Можно создать только нужные — каждый тип решает свою задачу.

CREATE STATISTICS s ON city, country FROM addressesпо умолчанию все три типа
ndistinctкол-во уникальных (city, country) пар — для GROUP BY
dependenciesстепень зависимости city → country (от 0 до 1)
mcvточные топ-N комбинаций (как обычный MCV, но multi-column)

Где живёт extended statistics

В pg_statistic_ext (определение) и pg_statistic_ext_data (собственно данные после ANALYZE). Аналог pg_statisticpg_stats_ext view для людей:

SELECT statistics_name, kind, columns, ...
FROM pg_stats_ext;

Главное: ANALYZE не строит extended stats автоматически. После CREATE STATISTICS нужно явно вызвать ANALYZE (на всю таблицу или конкретную), чтобы статистика заполнилась. После этого автовакуум подхватывает её регулярно.

Пример на customers

В нашем seed country распределён по id % 5 — равномерно. Это плохой пример для демонстрации корреляции. Сделаем синтетический случай прямо в песочнице.

Создаём таблицу addresses, где city и country сильно коррелированы. Датасет инициализируется ~5 секунд.

PostgreSQL

Здесь точная зависимость: city → country (каждый город принадлежит ровно одной стране). 10 уникальных городов, 5 стран, 10 уникальных пар.

Без extended stats: оценка для AND будет в 5 раз меньше реальной.

PostgreSQL

Оценка: sel(city='Москва') = 0.1, sel(country='RU') = 0.2, итог 0.02 → estimate ~1000. Реально: 5000 строк (все Москвы — это все RU из города 0). Ошибка 5x. На простом запросе это не критично, но в join’е может развалить план.

Добавляем extended statistics и пересобираем ANALYZE.

PostgreSQL

Теперь оценка должна стать близкой к реальной — rows ≈ 5000. Планировщик «понимает», что условия не независимы.

Как работает каждый тип

dependencies

Хранит коэффициент dep(city → country) — насколько значение country детерминируется значением city. От 0 (независимы) до 1 (country функционально зависит от city). Для нашего примера = 1.

Использование в оценке: если dep(city → country) = 1, то sel(city = 'Москва' AND country = 'RU') = sel(city = 'Москва') (правое условие «бесплатно», его уже гарантирует левое).

Это самая лёгкая статистика по объёму — несколько чисел на пару колонок.

ndistinct

Хранит n_distinct для комбинации колонок: сколько уникальных (city, country) пар существует. Без extended stats Postgres считает: n_distinct(city, country) = n_distinct(city) × n_distinct(country) = 10 × 5 = 50. Реально — 10 (потому что для каждого city существует ровно один country).

Это критично для GROUP BY city, country — без extended stats планировщик ожидает 50 групп, а реально 10. На больших таблицах это влияет на hash-table size и выбор Hash Aggregate vs Sort + GroupAggregate.

mcv

Multi-column MCV: точно хранит самые частые комбинации. Например, [(Москва, RU): 0.1, (Берлин, DE): 0.1, ...]. Это работает для предикатов с разным набором колонок (WHERE city = X, WHERE country = Y, WHERE city = X AND country = Y).

Самый точный тип, но и самый дорогой по объёму: до default_statistics_target комбинаций × количество строк хранения. Используй только когда dependencies недостаточно.

Когда какой тип нужен

  • dependencies — если у тебя есть один-ко-многим или функциональная зависимость (zip_code → city, model → brand).
  • ndistinct — если ты часто делаешь GROUP BY по нескольким колонкам, или join по составному ключу.
  • mcv — если распределение комбинаций сильно skewed и важна точная оценка на конкретных горячих значениях.

Создавать «всё сразу» — нормально для важных таблиц. Стоимость: лишний CPU на ANALYZE + килобайты на хранение.

Посмотрим, что лежит в pg_stats_ext для наших addresses.

PostgreSQL

В выводе ожидаем: n_distinct ~ {"1, 2": 2} (2 уникальные пары), dependencies — JSON вида {"1 => 2": 1.0} (city полностью определяет country).

Ловушки и нюансы

  1. Не работает для OR-предикатов. Extended stats применяются только к конъюнкциям (AND).
  2. Не помогает с join’ами. Это статистика внутри одной таблицы. Для join’ов корреляции остаются «магическими константами».
  3. Требует обновления. ANALYZE обновит, autovacuum тоже. Но если статистика устарела — оценки тоже устаревают.
  4. Помогает не всегда. Если корреляция слабая (dep = 0.3), эффект небольшой. Имеет смысл, когда зависимость близка к 1.
Проверка знанийKnowledge check
У тебя таблица events (user_id, event_type, country). 1M событий, 10K юзеров, 20 event_types, 50 стран. WHERE user_id = 42 AND event_type = 'login' AND country = 'RU' даёт estimate = 1, actual = 500. Реально каждый user живёт в одной стране и в основном делает login. Какую extended statistics создавать?
ОтветAnswer
Тут две корреляции: user_id → country (юзер живёт в одной стране) и user_id → event_type (юзер чаще логинится, чем покупает). Лучше всего создать CREATE STATISTICS s_events ON user_id, event_type, country FROM events с типом dependencies (для AND-предикатов) плюс mcv (если нужны точные оценки для конкретных user_id). ndistinct тут менее полезен — он для GROUP BY, не для WHERE. После ANALYZE оценка станет реалистичной. Альтернатива: переписать запрос с partial-индексом на (user_id, event_type) WHERE country = 'RU', но это привязка к одной стране. Extended stats — более общее решение.

Чек-лист

  • sel(A AND B) = sel(A) × sel(B) верно только при независимости. Корреляции → недооценка.
  • CREATE STATISTICS name (type, ...) ON col1, col2 FROM table — три типа: dependencies, ndistinct, mcv.
  • После CREATE нужен ANALYZE, чтобы статистика заполнилась.
  • dependencies — для AND-предикатов с функциональными зависимостями. Самый лёгкий.
  • ndistinct — для GROUP BY с несколькими колонками и join’ов по составному ключу.
  • mcv — multi-column MCV, самый точный и дорогой.
  • Смотреть результат: pg_stats_ext. Только AND, только внутри таблицы (не join).
Нормализация: 1NF, 2NF, 3NF и зачем это всё Что такое хеш-функция

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Какой тип extended statistics лучше всего поможет для запроса `SELECT count(*) FROM events GROUP BY user_id, event_type`, если эти колонки коррелированы (юзер обычно делает один-два типа событий)?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5