64.6. Функции оценки стоимости индекса
Функции amcostestimate
даётся информация, описывающая возможное сканирование индекса, включая списки предложений WHERE и ORDER BY, которые были выбраны как применимые с данным индексом. Она должна вернуть оценки стоимости обращения к индексу и избирательность предложений WHERE (то есть, процент строк основной таблицы, который будет получен в ходе сканирования индекса). Для простых случаев почти всю работу оценщика стоимости можно произвести, вызывая стандартные процедуры оптимизатора; смысл существования функции amcostestimate
в том, чтобы индексные методы доступа могли поделиться знаниями, специфичными для типа индекса, когда это может помочь улучшить стандартные оценки.
Каждая функция amcostestimate
должна иметь такую сигнатуру:
void amcostestimate (PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, double *indexCorrelation, double *indexPages);
Первые три параметра передают входные значения:
root
Информация планировщика о выполняемом запросе.
path
Рассматриваемый путь доступа к индексу. В нём действительны все поля, кроме значений стоимости и избирательности.
loop_count
Число повторений сканирования индекса, которое должно приниматься во внимание при оценке стоимости. Обычно оно будет больше одного, когда при соединении со вложенным циклом планируется параметризованное сканирование. Заметьте, что оценки стоимости тем не менее должны рассчитываться для всего одного сканирования; большие значения
loop_count
лишь дают основания предположить, что при многократном сканировании положительное влияние может оказать кеширование.
Последние пять параметров — указатели на переменные для выходных значений:
*indexStartupCost
Стоимость выполнения запуска индекса
*indexTotalCost
Общая стоимость использования индекса
*indexSelectivity
Избирательность индекса
*indexCorrelation
Коэффициент корреляции между порядком сканирования индекса и порядком записей в нижележащей таблице
*indexPages
Количество страниц индекса на уровне листьев
Заметьте, что функции оценки стоимости должны разрабатываться на C, а не на SQL или другом доступном процедурном языке, так как они должны обращаться к внутренним структурам данным планировщика/оптимизатора.
Стоимости обращения к индексу следует вычислять с использованием параметров, объявленных в src/backend/optimizer/path/costsize.c
: последовательная выборка дискового блока имеет стоимость seq_page_cost
, непоследовательная выборка — random_page_cost
, а стоимость обработки одной строки индекса обычно принимается равной cpu_index_tuple_cost
. Кроме того, за каждый оператор сравнения, вызываемый при обработке индекса, должна взиматься цена cpu_operator_cost
(особенно за вычисление собственно условий индекса).
Стоимость доступа должна включать стоимости всех дисковых и процессорных ресурсов, требующихся для сканирования самого индекса, но не стоимости извлечения или обработки строк основной таблицы, с которой связан индекс.
«Стоимость запуска» составляет часть общей стоимости сканирования, которая должна быть потрачена, прежде чем можно будет начать чтение первой строки. Для большинства индексов она может считаться нулевой, но для типов индексов с высокими затратами на запуск она может быть больше нуля.
Значение в indexSelectivity
должно показывать, какой процент строк основной таблицы ожидается получить при сканировании таблицы. В случае неточного запроса это обычно будет больше процента строк, действительно удовлетворяющих заданным ограничивающим условиям.
В indexCorrelation
записывается корреляция (в диапазоне от -1.0 до 1.0) между порядком записей в индексе и в таблице. Это значение будет корректировать оценку стоимости выборки строк из основной таблицы.
В indexPages
записывается число страниц на уровне листьев. Это помогает выбрать число исполнителей для параллельного сканирования индекса.
Когда loop_count
больше нуля, возвращаться должны средние значения, ожидаемые для одного сканирования индекса.
Оценка стоимости
Типичная процедура оценки выглядит следующим образом:
Рассчитать и вернуть процент строк родительской таблицы, которые будут посещены при заданных ограничивающих условиях. В отсутствие каких-либо знаний, специфичных для типа индекса, использовать стандартную функцию оптимизатора
clauselist_selectivity()
:*indexSelectivity = clauselist_selectivity(root, path->indexquals, path->indexinfo->rel->relid, JOIN_INNER, NULL);
Оценить число строк индекса, которые будут посещены при сканировании. Для многих типов индексов это будет произведение
indexSelectivity
и числа строк в индексе, но оно может быть и больше. (Заметьте, что размер индекса в страницах и строках можно узнать из структурыpath->indexinfo
.)Рассчитать число страниц индекса, которые будут получены при сканировании. Это может быть просто произведение
indexSelectivity
и размера индекса в страницах.Вычислить стоимость обращения к индексу. Универсальный оценщик может сделать следующее:
/* * Вообще предполагается, что страницы индекса будут считываться последовательно, * так что стоимость их чтения cost seq_page_cost, а не random_page_cost. * Также мы добавляем стоимость за вычисление условия индекса для каждой строки. * Все стоимости считаются пропорционально возрастающими при сканировании. */ cost_qual_eval(&index_qual_cost, path->indexquals, root); *indexStartupCost = index_qual_cost.startup; *indexTotalCost = seq_page_cost * numIndexPages + (cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;
Однако при таком расчёте не учитывается амортизация чтения индекса при повторном сканировании.
Оценить корреляцию индекса. Для простого упорядоченного индекса по одному полю её можно получить из pg_statistic. Если корреляция неизвестна, вернуть консервативную оценку — ноль (корреляция отсутствует).
Примеры функций оценки стоимости можно найти в src/backend/utils/adt/selfuncs.c
.