Перейти к основному содержимому

Примеры оценки строк

примечание

Эта страница переведена при помощи нейросети GigaChat.

В примерах ниже используются таблицы из базы данных регрессионного тестирования PostgreSQL. Результаты взяты из версии 8.3; поведение более ранних или поздних версий может отличаться. Также стоит учитывать, что ANALYZE использует случайную выборку при построении статистики, поэтому результаты могут немного изменяться после каждого нового запуска ANALYZE.

Начнем с простого запроса:

EXPLAIN SELECT * FROM tenk1;
QUERY PLAN
-------------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..458.00 rows=10000 width=244)

Количество страниц и строк хранится в pg_class:

SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';

relpages | reltuples
----------+-----------
358 | 10000

Эти значения отражают состояние таблицы на момент последнего VACUUM или ANALYZE. Планировщик дополнительно получает текущее число страниц (операция недорогая и не требует сканирования таблицы). Если фактическое значение отличается от relpages, то reltuples масштабируется, чтобы получить оценку текущего числа строк. В примере выше данные совпадают, поэтому результат соответствует reltuples.


Условие диапазона​

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000;
Bitmap Heap Scan on tenk1 (cost=24.06..394.64 rows=1007 width=244)
Recheck Cond: (unique1 < 1000)
-> Bitmap Index Scan on tenk1_unique1 (cost=0.00..23.80 rows=1007 width=0)
Index Cond: (unique1 < 1000)

Планировщик находит функцию селективности для оператора < в pg_operator (oprrest = scalarltsel). Функция использует гистограмму из pg_statistic. Более удобное представление доступно через pg_stats:

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='unique1';
{0,993,1997,3050,4040,5036,5957,7057,8029,9016,9995}

Значение 1000 попадает во второй сегмент (993–1997). Предположив линейное распределение значений, селективность вычисляется так:

selectivity = (1 + (1000 - 993)/(1997 - 993))/10
= 0.100697

Количество строк:

rows = 10000 * 0.100697 ≈ 1007

Условие равенства​

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'CRAAAA';
Seq Scan on tenk1 (cost=0.00..483.00 rows=30 width=244)
Filter: (stringu1 = 'CRAAAA'::name)

Функция селективности для = — eqsel. Для оценки равенства используется список наиболее распространенных значений (MCV):

SELECT null_frac, n_distinct, most_common_vals, most_common_freqs
FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';
null_frac | 0
n_distinct | 676
most_common_vals | {EJAAAA,BBAAAA,CRAAAA,FCAAAA,FEAAAA,...}
most_common_freqs | {0.00333333,0.003,0.003,0.003,0.003,...}

Значение CRAAAA есть в списке, селективность равна частоте:

selectivity = 0.003
rows = 10000 * 0.003 = 30

Если константа отсутствует в MCV, применяется формула:

selectivity = (1 - sum(mcv_freqs)) / (n_distinct - num_mcv)

Таким образом распределяется оставшаяся доля значений равномерно между прочими уникальными значениями.


Уточнение работы scalarltsel​

Для уникальных столбцов, таких как unique1, список MCV отсутствует, поэтому используется только гистограмма. Для неуникальных столбцов селективность оценивается в два шага:

  • сначала точная проверка значений из MCV,
  • затем расчет по гистограмме для оставшейся части выборки.

Оценки комбинируются, что позволяет повысить точность в случае неравномерного распределения данных.


Несколько условий​

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000 AND stringu1 = 'xxx';
Bitmap Heap Scan on tenk1 (cost=23.80..396.91 rows=1 width=244)
Recheck Cond: (unique1 < 1000)
Filter: (stringu1 = 'xxx'::name)
-> Bitmap Index Scan on tenk1_unique1 (cost=0.00..23.80 rows=1007 width=0)
Index Cond: (unique1 < 1000)

Планировщик предполагает независимость условий и перемножает селективности:

selectivity = 0.100697 * 0.0014559 ≈ 0.0001466
rows = 10000 * 0.0001466 ≈ 1

Пример соединения​

EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 50 AND t1.unique2 = t2.unique2;
Nested Loop (cost=4.64..456.23 rows=50 width=488)
-> Bitmap Heap Scan on tenk1 t1 ...
-> Index Scan using tenk2_unique2 on tenk2 t2 ...

Для unique1 < 50 селективность оценивается через гистограмму:

selectivity = (0 + (50 - 0)/(993 - 0))/10 = 0.005035
rows = 10000 * 0.005035 = 50

Соединение по unique2 обрабатывается функцией eqjoinsel. Так как значения уникальны, MCV отсутствует. Селективность вычисляется через количество различных значений:

selectivity = (1 - null_frac1) * (1 - null_frac2) / max(n_distinct1, n_distinct2)
= 1 * 1 / 10000
= 0.0001

Оценка числа строк соединения:

rows = (50 * 10000) * 0.0001 = 50

Где искать реализацию​

  • Оценка размера таблицы: src/backend/optimizer/util/plancat.c
  • Общая логика селективности условий: src/backend/optimizer/path/clausesel.c
  • Функции селективности операторов: src/backend/utils/adt/selfuncs.c