17 Статистика
PostgreSQL 18 · Издание 2026 года · Китайский перевод доступен для PostgreSQL 18
17.1. Базовая статистика
Базовая статистика уровня отношения 1 хранится в таблице pg_class системного каталога. К ней относятся:
число строк в отношении (
reltuples);размер отношения, в страницах (
relpages);количество страниц, отмеченных в карте видимости (
relallvisible). (с. 32)
Вот эти значения на примере таблицы flights:
Значение reltuples используется в качестве оценки кардинальности, когда запрос не накладывает никаких условий на строки таблицы:
Статистика собирается при анализе, ручном или автоматическом2. Однако ввиду особой важности базовая статистика рассчитывается также при выполнении некоторых операций (VACUUM FULL и CLUSTER3, CREATE INDEX и REINDEX 4) и уточняется при очистке 5. (с. 137)
Для анализа случайно выбираются 300 × default_statistics_target строк. Поскольку размер выборки, достаточной для построения статистики заданной точности, слабо зависит от объема анализируемых данных, размер таблицы не учитывается6. (по умолчанию: 100)
Строки выбираются из такого же количества (300 × default_statistics_target) случайных страниц 7. Конечно, для небольшой таблицы количество прочитанных страниц и выбранных для анализа строк может оказаться меньше.
Поскольку в достаточно больших таблицах статистика собирается не по всем строкам, оценки могут немного расходиться с реальностью. Это нормально: статистика в любом случае не может все время быть точной, если данные изменяются. Для выбора адекватного плана обычно достаточно попадания в порядок.
Создадим копию таблицы flights с отключенной автоочисткой, чтобы управлять временем выполнения анализа:
Для новой таблицы никакой статистики еще нет:
Значение reltuples = −1 позволяет отличить таблицу, для которой статистика ни разу не собиралась, от действительно пустой таблицы без строк. (v. 14)
Но с большой вероятностью в таблицу будут добавлены какие-то строки сразу после создания. Поэтому, находясь в неведении, планировщик считает, что таблица занимает 10 страниц:
Количество строк рассчитывается исходя из размера одной строки; он отображается в плане запроса как width. Обычно для оценки используется среднее значение, вычисляемое при анализе, но в этом случае, поскольку статистика отсутствует, размер строки вычисляется приблизительно с учетом типов данных каждого из столбцов8.
Теперь скопируем данные из таблицы flights и выполним анализ:
Сейчас статистика совпадает с реальным количеством строк (размер таблицы таков, что статистика собирается по полным данным):
Значение relallvisible используется при оценке стоимости сканирования только индекса. Оно обновляется при очистке: (с. 422)
Теперь удвоим количество строк, не собирая статистику, и проверим оценку кардинальности в плане запроса:
Оценка оказалась точна, несмотря на устаревшие сведения в pg_class:
Дело в том, что планировщик повышает точность оценки, масштабируя значение reltuples в соответствии с отклонением реального размера файла данных от значения relpages9. Поскольку размер файла вырос в два раза по сравнению с relpages, количество строк скорректировалось исходя из предположения, что плотность данных не изменилась:
Конечно, такая корректировка работает не всегда (например, если удалить часть строк, оценка не изменится), но в ряде случаев позволяет «продержаться» до прихода анализа при крупных изменениях.
17.2. Неопределенные значения
Неопределенные значения, порицаемые теоретиками 10, играют тем не менее важную роль в реляционных базах данных как удобный способ представления того факта, что значение не существует или неизвестно.
Но особое значение требует и особого к себе отношения. Помимо теоретических неувязок, возникает множество сугубо практических сложностей, с которыми приходится считаться. Обычная булева логика превращается в трехзначную, а конструкция NOT IN ведет себя неожиданно. Непонятно, должны ли неопределенные значения считаться меньше обычных значений или больше (отсюда специальные предложения NULLS FIRST и NULLS LAST для сортировки). Не так уж очевидно, должны ли неопределенные значения учитываться в агрегатных функциях. Поскольку, строго говоря, неопределенные значения вовсе не являются значениями, то для их учета планировщику необходима дополнительная информация.
Помимо самой простой, базовой статистики на уровне отношений, при анализе собирается статистика для каждого столбца отношения. Она хранится в таблице системного каталога pg_statistic11, но значительно проще пользоваться представлением pg_stats, которое показывает информацию в более удобном виде.
Доля неопределенных значений как раз относится к статистике уровня столбца; вычисленное при анализе значение показывает атрибут null_frac.
Например, чтобы найти еще не отправившиеся рейсы, можно воспользоваться тем, что время их вылета не определено:
Оценка вычисляется как общее число строк, умноженное на долю NULL:
Точное значение:
17.3. Уникальные значения
Поле n_distinct представления pg_stats показывает количество уникальных значений в столбце.
Если значение n_distinct отрицательно, то модуль этого числа равен не количеству, а доле уникальных значений. Например, −1 означает, что все значения в столбце уникальны, а −3 говорит о том, что каждое значение в среднем встречается в трех строках. Анализатор использует доли, когда вычисленное при анализе количество уникальных значений превышает 10 % от
общего количества строк; в этом случае пропорция, скорее всего, сохранится и при дальнейшем изменении данных12.
Количество уникальных значений используется во всех случаях, которые предполагают равномерное распределение данных. Например, при оценке кардинальности условия «столбец = выражение», когда значение выражения неизвестно на этапе планирования, считается, что выражение может принимать любое из возможных значений столбца с равной вероятностью 13:
Здесь узел плана InitPlan выполняется один раз, и вычисленное значение используется в основном плане.
Если количество уникальных значений вычисляется неверно (из-за ограниченности выборки, по которой проводится анализ), это количество можно указать для столбца явно:
Если бы все данные были всегда распределены равномерно, этой информации (дополненной минимальным и максимальным значениями) было бы достаточно. Но при неравномерном распределении, которое на практике встречается гораздо чаще, такая оценка не будет точна:
17.4. Наиболее частые значения
Для уточнения оценки при неравномерном распределении собирается статистика по наиболее часто встречающимся значениям (most common values, MCV) и частоте их появления. Представление pg_stats показывает два этих массива в столбцах most_common_vals и most_common_freqs.
Вот пример такой статистики по частоте использования различных типов самолетов:
Для оценки селективности условия «столбец = значение» достаточно найти значение в массиве most_common_vals и взять частоту из элемента массива most_common_freqs с тем же номером 14:
Очевидно, что такая оценка будет близка к точному значению:
Список частых значений используется и для оценки селективности условий с неравенствами. Например, для условия вида «столбец < значение» надо найти в most_common_vals все значения, меньшие искомого, и просуммировать частоты из most_common_freqs15.
Статистика частых значений отлично работает, когда количество различных значений не очень велико. Максимальный размер массивов определяется тем же параметром default_statistics_target, который ограничивает размер случайной выборки строк для анализа. (по умолчанию: 100)
В некоторых случаях может иметь смысл увеличить значение параметра по умолчанию, чтобы расширить список частых значений и повысить точность оценок. Это можно сделать на уровне отдельного столбца:
При этом увеличится и размер выборки, но только для указанной таблицы.
Поскольку в массиве частых значений сохраняются сами значения, массив может занимать довольно много места. Чтобы чрезмерно не увеличивать pg_statistic и не нагружать планировщик бесполезной работой, из анализа и статистики исключаются значения, размер которых превышает 1 Кбайт. К тому же такие большие значения, скорее всего, уникальны и поэтому все равно не должны попасть в most_common_vals.
17.5. Гистограмма
Когда число различных значений слишком велико, чтобы записать их в массив, на помощь приходит гистограмма. Гистограмма состоит из нескольких корзин, в которые помещаются значения. Количество корзин ограничено все тем же параметром default_statistics_target.
Ширина корзин выбирается так, чтобы в каждую попало примерно одинаковое количество значений (на рисунке этому свойству соответствует одинаковая площадь больших заштрихованных прямоугольников). При этом учитываются только те значения, которые не попали в список наиболее частых. При таком построении суммарная частота значений одной (любой) корзины равна
Гистограмма хранится в поле histogram_bounds представления pg_stats как массив значений, ограничивающих корзины:
Гистограмма используется, в частности, для оценки селективности операторов «больше» или «меньше» вместе со списком наиболее частых значений16. Рассмотрим пример — количество посадочных талонов, выданных на дальние ряды:
Номер места специально выбран так, что он лежит точно на границе корзин гистограммы.
Оценка селективности такого условия будет равна , где N — количество корзин, значения в которых удовлетворяют условию (то есть находятся справа от значения). При этом необходимо учесть, что наиболее частые значения не входят в гистограмму.
Вообще говоря, неопределенные значения тоже не входят в гистограмму, но в столбце seat_no их быть не может:
Сначала найдем долю наиболее частых значений, которые удовлетворяют условию:
Общая доля наиболее частых значений (не учитываемая гистограммой) составляет:
Поскольку интервал занимает ровное количество корзин гистограммы (из 100 возможных), получаем следующую оценку:
В общем случае, когда значение лежит не на границе, с помощью линейной интерполяции учитывается доля корзины, в которой находится значение.
Точное значение составляет:
Увеличение параметра default_statistics_target может улучшить оценку, однако, как показывает пример, в сочетании со списком наиболее частых значений гистограмма обычно дает хороший результат даже при большом количестве уникальных значений в столбце:
Увеличение точности оценки имеет смысл только в том случае, когда оно приводит к построению более качественного плана. Бездумное увеличение параметра может замедлить выполнение анализа и работу планировщика, ничего не улучшив. С другой стороны, уменьшение параметра (вплоть до нуля) может ускорить анализ и планирование, но может и послужить причиной плохих планов. Такая «экономия» обычно неоправданна.
17.6. Статистика для нескалярных типов данных
Для нескалярных типов данных может собираться статистика по распределению не только самих значений, но и элементов, из которых эти значения состоят. Это позволяет более точно планировать запросы с участием столбцов не в первой нормальной форме.
- Массивы
most_common_elemsиmost_common_elem_freqsпоказывают список наиболее частых элементов и их частоты.
Статистика собирается и используется при оценке селективности для массивов 17и типа данных tsvector 18.
- Массив
elem_count_histogramпоказывает гистограмму количества уникальных элементов в значении.
Статистика собирается и используется при оценке селективности только для массивов.
- Для непустых диапазонов собираются гистограммы распределения нижних и верхних границ (массив
range_bounds_histogram) и длины (массивrange_length_histogram). Отдельно отмечается доля пустых диапазонов (столбецrange_empty_frac). Эти данные используются для оценки селективности различных операций с диапазонными типами 19. Такая же статистика используется и для мультидиапазонных типов20. (v. 17) (v. 14)
17.7. Средний размер поля
Поле avg_width представления pg_stats показывает средний размер значений в столбце. Конечно, для таких типов, как integer или char(3), этот показатель всегда одинаков, но для типов данных с переменной длиной, таких как text, он может сильно отличаться от столбца к столбцу:
Эта статистика используется для оценки объема памяти, необходимой некоторым операциям, например сортировке или хешированию.
17.8. Корреляция
Поле correlation представления pg_stats показывает корреляцию между физическим расположением данных и логическим порядком в смысле операций сравнения. Если значения хранятся строго по возрастанию, корреляция будет близка к единице; если по убыванию — к минус единице. Чем более хаотично расположены данные на диске, тем ближе значение к нулю. (с. 546)
Корреляция используется для оценки стоимости индексного сканирования. Для столбца coordinates эта статистика не собирается, поскольку для типа данных point не определены операторы «больше» и «меньше». (с. 415)
17.9. Статистика по выражению
Обычно статистика по столбцу может использоваться, только если в операции сравнения слева или справа от оператора фигурирует сам столбец, а не выражение. Например, планировщик не знает, как изменится статистика после вычисления функции от столбца, и поэтому для условия «вызов-функции = константа» всегда использует фиксированную оценку в 0,5 % 21:
Планировщику неизвестна семантика даже стандартных функций. Хотя нам из общих соображений понятно, что рейсов, совершенных в январе, будет примерно от общего количества, то есть на порядок больше спрогнозированного значения.
PostgreSQL позволяет создать вспомогательную функцию для планировщика22, которая подскажет ему ожидаемое количество строк на выходе некоторой функции при заданных аргументах. Поэтому про некоторые встроенные функции (например, generate_series) планировщик кое-что знает. Но далеко не все и не про все.
Чтобы исправить ситуацию, надо собрать статистику не по столбцу таблицы, а по выражению. Это можно сделать двумя способами.
Расширенная статистика по выражению
Первый вариант — использовать расширенную статистику 23 по выражению. Такая статистика не собирается автоматически; необходимо вручную создать объект базы данных командой CREATE STATISTICS: (v. 14)
После сбора статистики оценка исправляется:
Чтобы собранная статистика использовалась, выражение в условии запроса должно быть записано в том же виде, что и в команде CREATE STATISTICS.
Размер собираемой расширенной статистики можно изменить отдельно командой ALTER STATISTICS, например: (v. 13)
Общая информация о расширенной статистике хранится в таблице системного каталога pg_statistic_ext, а собственно собранная статистика — в отдельной таблице pg_statistic_ext_data. Смысл такого разделения состоит в возможности ограничения доступа пользователей к чувствительной информации. (v. 12)
Доступную пользователю расширенную статистику по выражению можно посмотреть в более удобном виде с помощью специального представления:
Статистика для индекса по выражению
Второй способ исправить оценки кардинальности — воспользоваться тем, что при создании индекса по выражению для него собирается отдельная статистика, как для таблицы. Это удобно, если индекс действительно нужен. (с. 401)
Статистика для индексов по выражению хранится так же, как статистика по таблице. Например, из pg_stats можно получить количество уникальных значений, указав в качестве tablename имя индекса:
Изменить точность статистики в случае индекса можно командой ALTER INDEX. Для этого сначала может потребоваться узнать, как называется столбец, соответствующий выражению. Например:
17.10. Многовариантная статистика
PostgreSQL дает возможность собирать многовариантную статистику, охватывающую не один, а несколько столбцов таблицы. Для этого необходимо вручную создать соответствующую расширенную статистику.
Реализовано три вида многовариантной статистики.
Функциональные зависимости между столбцами
Если значения в одном столбце определяются (полностью или частично) значениями другого столбца и в запросе указаны условия на оба таких столбца, оценка кардинальности будет занижена.
Рассмотрим запрос с двумя условиями:
Оценка оказывается сильно заниженной:
Это известная проблема коррелированных предикатов. Планировщик полагается на то, что предикаты независимы, и вычисляет общую селективность как произведение селективностей условий, объединенных логическим «и». Это хорошо видно в приведенном плане: оценка в узле Bitmap Index Scan, полученная по условию на столбец flight_no, существенно уменьшается после фильтрации по условию на столбец departure_airport в узле Bitmap Heap Scan. (с. 336)
Однако мы понимаем, что номер рейса однозначно определяет аэропорты: фактически второе условие избыточно (конечно, если аэропорт указан правильно). В таких случаях расширенная статистика по функциональным зависимостям может улучшить оценку.
Создадим расширенную статистику по функциональной зависимости между двумя столбцами:
При очередном анализе таблицы желаемая статистика будет собрана, и оценка улучшится:
В системном каталоге собранную статистику можно посмотреть следующим образом:
Числа 2 и 5 — номера столбцов таблицы из pg_attribute. Значения определяют степень функциональной зависимости: от 0 (зависимости нет) до 1 (значения в первом столбце полностью определяют значения во втором).
Многовариантное число различных значений
Информация о количестве уникальных комбинаций значений из нескольких столбцов позволяет точнее оценить кардинальность группировки по нескольким столбцам.
Например, количество возможных пар аэропортов отправления и прибытия оценивается планировщиком как квадрат количества аэропортов, но реальное значение сильно меньше, поскольку далеко не каждая пара аэропортов соединена прямым рейсом:
Создадим и соберем расширенную статистику по числу разных значений:
Теперь оценка кардинальности исправилась:
Собранную статистику можно увидеть в системном каталоге:
Многовариантные списки частых значений
При неравномерном распределении значений одного только знания функциональной зависимости может быть недостаточно, поскольку оценка существенно зависит от конкретной пары значений. Например, планировщик ошибается, оценивая количество рейсов из Шереметьева, выполняемых Боингом 737: (v. 12)
В таком случае оценку можно уточнить, собирая статистику по многовариантным спискам частых значений 24:
Новая оценка кардинальности гораздо точнее:
Для ее получения планировщик воспользовался частотой, сохраненной в системном каталоге:
В многовариантном списке, как и в обычном списке частых значений, сохраняется default_statistics_target значений (если параметр задан на уровне столбцов, то используется наибольшее значение). (по умолчанию: 100)
Как и для расширенной статистики по выражению, при необходимости можно изменить размер списка: (v. 13)
Во всех примерах я использовал только два столбца, но многовариантную статистику можно создавать и по большему количеству.
В одном объекте можно комбинировать статистику разных типов, указывая их через запятую (а если не указать тип, для заданных столбцов будут собираться сразу все возможные варианты статистики).
Более того, как и в статистике по выражению, в многовариантной статистике тоже можно использовать произвольные выражения, а не только имена столбцов. (v. 14)
backend/commands/analyze.c, функция do_analyze_rel. ↩︎
backend/commands/cluster.c, функция copy_table_data. ↩︎
backend/catalog/heap.c, функция index_update_stats. ↩︎
backend/access/heap/vacuumlazy.c, функция heap_vacuum_rel. ↩︎
backend/commands/analyze.c, функция std_typanalyze. ↩︎
backend/commands/analyze.c, функция acquire_sample_rows; backend/utils/misc/sampling.c. ↩︎
backend/access/table/tableam.c, функция table_block_relation_estimate_size. ↩︎
backend/access/table/tableam.c, функция table_block_relation_estimate_size. ↩︎
backend/commands/analyze.c, функция compute_distinct_stats. ↩︎
backend/utils/adt/selfuncs.c, функция var_eq_non_const. ↩︎
backend/utils/adt/selfuncs.c, функция var_eq_const. ↩︎
backend/utils/adt/selfuncs.c, функция scalarineqsel. ↩︎
backend/utils/adt/selfuncs.c, функция ineq_histogram_selectivity. ↩︎
postgrespro.ru/docs/postgresql/18/arrays; backend/utils/adt/array_typanalyze.c; backend/utils/adt/array_selfuncs.c. ↩︎
postgrespro.ru/docs/postgresql/18/datatype-textsearch; backend/tsearch/ts_typanalyze.c; backend/tsearch/ts_selfuncs.c. ↩︎
postgrespro.ru/docs/postgresql/18/rangetypes; backend/utils/adt/rangetypes_typanalyze.c; backend/utils/adt/rangetypes_selfuncs.c. ↩︎
backend/utils/adt/selfuncs.c, функция eqsel. ↩︎
postgrespro.ru/docs/postgresql/18/planner-stats#PLANNER-STATS-EXTENDED; backend/statistics/README. ↩︎