Примеры оценки строк
Эта страница переведена при помощи нейросети 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