61.2. Функции метода доступа к индексам#
61.2. Функции метода доступа к индексам #
Все функции построения и поддержки индекса, которые должен предоставить метод доступа к индексу в IndexAmRoutine
, включают:
IndexBuildResult * ambuild (Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo);
Построить новый индекс. Отношение индекса было создано физически, но оно пустое. Его необходимо заполнить любыми фиксированными данными, которые требует метод доступа, а также записями для всех уже существующих кортежей в таблице. Обычно функция ambuild
вызывает table_index_build_scan()
для сканирования таблицы существующих кортежей и вычисления ключей, которые необходимо вставить в индекс. Функция должна вернуть структуру, выделенную с помощью palloc, содержащую статистику о новом индексе.
void ambuildempty (Relation indexRelation);
Построить пустой индекс и записать его в форк инициализации (INIT_FORKNUM
) данного отношения. Этот метод вызывается только для нефиксируемых индексов; пустой индекс, записанный в форк инициализации, будет скопирован в основной форк отношения при каждом перезапуске сервера.
bool aminsert (Relation indexRelation, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRelation, IndexUniqueCheck checkUnique, bool indexUnchanged, IndexInfo *indexInfo);
Вставьте новый кортеж в существующий индекс. Массивы values
и isnull
содержат значения ключей, которые должны быть проиндексированы, а heap_tid
- TID, который должен быть проиндексирован. Если метод доступа поддерживает уникальные индексы (его флаг amcanunique
равен true), то checkUnique
указывает тип проверки уникальности, которую нужно выполнить. Это может варьироваться в зависимости от того, является ли ограничение уникальности отложенным; см. Раздел 61.5 для получения подробной информации. Обычно метод доступа требуется только параметр heapRelation
при выполнении проверки уникальности (поскольку в этом случае ему придется просматривать кучу, чтобы проверить жизнеспособность кортежа).
Значение Boolean indexUnchanged
дает подсказку
о природе кортежа, который нужно индексировать. Когда оно истинно,
кортеж является дубликатом некоторого существующего кортежа в индексе. Новый
кортеж является логически неизмененной версией кортежа MVCC-преемника. Это
происходит, когда выполняется UPDATE
, который не
изменяет ни одного столбца, охваченного индексом, но, тем не менее, требует
новой версии в индексе. Индекс AM может использовать эту подсказку, чтобы решить
применить удаление индекса снизу вверх в частях индекса, где накапливается много
версий одной и той же логической строки. Обратите внимание, что обновление неключевого
столбца или столбца, который появляется только в предикате частичного индекса, не
влияет на значение indexUnchanged
. Основной код
определяет значение indexUnchanged
для каждого кортежа, используя метод с низкими
накладными расходами, который допускает как ложные срабатывания, так и ложные отрицания.
Индексы AM не должны рассматривать indexUnchanged
как
авторитетный источник информации о видимости или версионности кортежа.
Логическое значение результата функции имеет значение только тогда, когда
checkUnique
равно UNIQUE_CHECK_PARTIAL
.
В этом случае истинный результат означает, что новая запись известно уникальна, тогда как
ложный результат означает, что она может быть неуникальной (и должна быть запланирована отложенная проверка уникальности).
Для других случаев рекомендуется постоянный ложный результат.
Возможно, некоторые индексы не индексируют все кортежи. Если кортеж не должен быть проиндексирован, функция aminsert
должна просто вернуться без выполнения каких-либо действий.
Если AM индекса хочет кешировать данные между последовательными вставками индекса в рамках одного SQL-запроса, он может выделить пространство в indexInfo->ii_Context
и сохранить указатель на данные в indexInfo->ii_AmCache
(который изначально будет равен NULL).
IndexBulkDeleteResult * ambulkdelete (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state);
Удалить кортеж(и) из индекса. Это операция “массового удаления”, которая предполагает просмотр всего индекса и проверку каждой записи, чтобы определить, должна ли она быть удалена.
Переданная функция callback
должна быть вызвана в стиле callback(
, чтобы определить, должна ли быть удалена конкретная запись индекса, идентифицируемая своим TID. Должна возвращать либо NULL, либо структуру, выделенную с помощью palloc, содержащую статистику о влиянии операции удаления. Если необходимо передать информацию в TID
, callback_state) returns boolamvacuumcleanup
, можно вернуть NULL.
Из-за ограниченного значения maintenance_work_mem
,
функцию ambulkdelete
может потребоваться вызывать несколько раз, когда требуется удалить много кортежей. Аргумент stats
является результатом предыдущего вызова для этого индекса (для первого вызова внутри операции VACUUM
он равен NULL). Это позволяет AM накапливать статистику на протяжении всей операции. Обычно функция ambulkdelete
изменяет и возвращает ту же структуру, если переданный аргумент stats
не является NULL.
IndexBulkDeleteResult * amvacuumcleanup (IndexVacuumInfo *info, IndexBulkDeleteResult *stats);
Очистка после операции VACUUM
(ноль или более вызовов ambulkdelete
). Это не обязано выполнять никаких действий, кроме возврата статистики индекса, но оно может выполнять массовую очистку, такую как освобождение пустых страниц индекса. stats
- это то, что вернул последний вызов ambulkdelete
, или NULL, если ambulkdelete
не вызывался, потому что не было необходимости удалять кортежи. Если результат не является NULL, он должен быть структурой, выделенной с помощью palloc. Содержащаяся в нем статистика будет использоваться для обновления pg_class
и будет отображаться при вызове VACUUM
с флагом VERBOSE
. Если индекс не изменился во время операции VACUUM
, можно вернуть NULL, но в противном случае должна быть возвращена правильная статистика.
amvacuumcleanup
также будет вызвана по завершении операции ANALYZE
. В этом случае stats
всегда равно NULL, и любое возвращаемое значение будет проигнорировано. Этот случай можно отличить, проверив info->analyze_only
. Рекомендуется, чтобы метод доступа в таком вызове ничего не делал, кроме очистки после вставки, и только в рабочем процессе автоочистки.
bool amcanreturn (Relation indexRelation, int attno);
Проверяет, может ли индекс поддерживать индексные сканирования только по индексу на
указанном столбце, возвращая исходное индексное значение столбца.
Номер атрибута начинается с 1, то есть первый столбец имеет attno равный 1.
Возвращает true, если поддерживается, иначе false.
Эта функция всегда должна возвращать true для включенных столбцов
(если они поддерживаются), так как нет смысла включать столбец, который нельзя получить.
Если метод доступа не поддерживает индексные сканирования только по индексу вообще,
поле amcanreturn
в его структуре IndexAmRoutine
может быть установлено в NULL.
void amcostestimate (PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, double *indexCorrelation, double *indexPages);
Оцените затраты на сканирование индекса. Эта функция полностью описана в разделе Раздел 61.6, ниже.
bytea * amoptions (ArrayType *reloptions, bool validate);
Разбор и проверка массива reloptions для индекса. Это вызывается только
когда существует ненулевой массив reloptions для индекса.
Параметр reloptions
- это массив типа text
, содержащий записи вида
name
=
value
.
Функция должна построить значение типа bytea
, которое будет скопировано
в поле rd_options
записи relcache индекса.
Содержимое данных значения типа bytea
доступно для определения
методом доступа; большинство стандартных методов доступа используют структуру
StdRdOptions
.
Когда параметр validate
равен true, функция должна сообщить подходящее
сообщение об ошибке, если какие-либо из опций не распознаны или имеют недопустимые
значения; когда параметр validate
равен false, недопустимые записи должны быть
молча проигнорированы. (Параметр validate равен false при загрузке опций,
уже сохраненных в pg_catalog
; недопустимая запись может быть найдена только если
метод доступа изменил свои правила для опций, и в
этом случае игнорирование устаревших записей является уместным).
Возвращение NULL допустимо, если требуется использовать поведение по умолчанию.
bool amproperty (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull);
Метод amproperty
позволяет индексным методам изменить
стандартное поведение функции pg_index_column_has_property
и функций.
Если индексный метод не имеет специального поведения для запросов свойств индекса,
поле amproperty
в структуре IndexAmRoutine
может быть установлено в NULL.
В противном случае, метод amproperty
будет вызван с
параметрами index_oid
и attno
, оба равны нулю, для
вызовов функции pg_indexam_has_property
,
или с параметром index_oid
равным допустимому значению и attno
равным нулю для
вызовов функции pg_index_has_property
,
или с параметром index_oid
равным допустимому значению и attno
большим
нуля для вызовов функции pg_index_column_has_property
.
prop
- это значение перечисления, идентифицирующее проверяемое свойство,
а propname
- это исходная строка имени свойства.
Если ядро не распознает имя свойства,
то prop
будет равно AMPROP_UNKNOWN
.
Индексные методы могут определить собственные имена свойств,
проверяя соответствие propname
(используйте pg_strcasecmp
для сравнения, чтобы быть согласованным с ядром); для имен, известных ядру,
лучше проверять prop
.
Если метод amproperty
возвращает true
,
то он определил результат проверки свойства: он должен установить *res
в логическое значение, которое нужно вернуть, или установить *isnull
в true
, чтобы вернуть NULL. (Обе переменные,
на которые ссылается, инициализируются значением false
перед вызовом).
Если метод amproperty
возвращает false
,
то ядро продолжит свою нормальную логику для определения
результата проверки свойства.
Все методы доступа, поддерживающие операторы сортировки, должны реализовывать проверку свойства AMPROP_DISTANCE_ORDERABLE
, так как основной код не знает, как это сделать, и будет возвращать NULL. Также может быть выгодно реализовать проверку свойства AMPROP_RETURNABLE
, если это можно сделать более дешево, чем открытие индекса и вызов amcanreturn
, что является поведением по умолчанию основного кода. Поведение по умолчанию должно быть удовлетворительным для всех остальных стандартных свойств.
char * ambuildphasename (int64 phasenum);
Вернуть текстовое имя заданного номера фазы сборки.
Номера фаз отображаются во время сборки индекса через интерфейс pgstat_progress_update_param
.
Затем имена фаз отображаются в представлении pg_stat_progress_create_index
.
bool amvalidate (Oid opclassoid);
Проверьте записи в каталоге для указанного класса операторов, насколько это возможно для метода доступа. Например, это может включать проверку наличия всех необходимых вспомогательных функций.
Функция amvalidate
должна возвращать false, если класс операторов недействителен. Проблемы должны быть сообщены с помощью сообщений ereport
, обычно на уровне INFO
.
void amadjustmembers (Oid opfamilyoid, Oid opclassoid, List *operators, List *functions);
Проверьте предлагаемые новые операторы и функции, принадлежащие семейству операторов, насколько это возможно для метода доступа, и установите их типы зависимостей, если значение по умолчанию не удовлетворяет. Это выполняется во время команды CREATE OPERATOR CLASS
и во время команды ALTER OPERATOR FAMILY ADD
; в последнем случае opclassoid
равен InvalidOid
. Аргументы List
являются списками структур OpFamilyMember
, определенных в файле amapi.h
.
Тесты, выполняемые этой функцией, обычно являются подмножеством тестов, выполняемых функцией amvalidate
, поскольку функция amadjustmembers
не может предполагать, что она видит полный набор членов. Например, было бы разумно проверить сигнатуру поддерживающей функции, но не проверять, предоставлены ли все необходимые поддерживающие функции. Любые проблемы могут быть сообщены с помощью генерации ошибки.
Поля, связанные с зависимостью, структур OpFamilyMember
инициализируются основным кодом для создания жестких зависимостей от opclass, если это CREATE OPERATOR CLASS
, или мягких зависимостей от opfamily, если это ALTER OPERATOR FAMILY ADD
. Функция amadjustmembers
может настроить эти поля, если другое поведение более подходит. Например, GIN, GiST и SP-GiST всегда устанавливают для операторов мягкие зависимости от opfamily, поскольку связь между оператором и opclass относительно слаба в этих типах индексов; поэтому разумно разрешить добавление и удаление операторов. Опциональные поддерживающие функции обычно также имеют мягкие зависимости, чтобы их можно было удалить при необходимости.
Целью индекса, конечно, является поддержка сканирования для кортежей, соответствующих индексируемому условию WHERE
, часто называемому квалификатором или ключом сканирования. Семантика сканирования индекса подробно описана в разделе Раздел 61.3, ниже. Метод доступа к индексу может поддерживать “обычные” сканирования индекса, “битовые” сканирования индекса или оба вида. Функции, связанные со сканированием, которые метод доступа к индексу должен или может предоставлять, включают:
IndexScanDesc ambeginscan (Relation indexRelation, int nkeys, int norderbys);
Подготовка к сканированию индекса. Параметры nkeys
и norderbys
указывают количество условий и операторов сортировки, которые будут использоваться в сканировании; это может быть полезно для выделения места. Обратите внимание, что фактические значения ключей сканирования еще не предоставлены. Результат должен быть структурой, выделенной с помощью palloc. По реализационным причинам метод доступа к индексу должен создавать эту структуру, вызывая RelationGetIndexScan()
. В большинстве случаев ambeginscan
мало что делает, кроме вызова этой функции и, возможно, получения блокировок; интересные части начала сканирования индекса находятся в функции amrescan
.
void amrescan (IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys);
Начать или перезапустить сканирование индекса, возможно, с новыми ключами сканирования. (Для перезапуска с использованием ранее переданных ключей, для keys
и/или orderbys
передается NULL). Обратите внимание, что количество ключей или операторов сортировки не может быть больше, чем то, что было передано в ambeginscan
. На практике функция перезапуска используется, когда новый внешний кортеж выбирается с помощью соединения вложенным циклом, и поэтому требуется новое значение сравнения ключа, но структура ключа сканирования остается неизменной.
bool amgettuple (IndexScanDesc scan, ScanDirection direction);
Получить следующий кортеж в данном сканировании, перемещаясь в заданном направлении (вперед или назад в индексе). Возвращает true, если был получен кортеж, false, если не осталось соответствующих кортежей. В случае true, кортеж TID сохраняется в структуре scan
. Обратите внимание, что “успех” означает только то, что индекс содержит запись, соответствующую ключам сканирования, а не то, что кортеж все еще существует в куче или пройдет проверку снимка вызывающей стороны. В случае успеха amgettuple
также должна установить scan->xs_recheck
в true или false. False означает, что уверенность в том, что запись в индексе соответствует ключам сканирования. True означает, что это неизвестно, и условия, представленные ключами сканирования, должны быть проверены снова по кортежу в куче после его получения. Это обеспечивает поддержку “неполных” операторов индекса. Обратите внимание, что проверка будет распространяться только на условия сканирования; частичный предикат индекса (если есть) никогда не проверяется вызывающими amgettuple
.
Если индекс поддерживает только сканирование индекса (т.е. amcanreturn
возвращает true для любого из его столбцов), то при успешном выполнении AM также должен проверить scan->xs_want_itup
, и если это верно, он должен вернуть исходные данные для записи индекса. Столбцы, для которых amcanreturn
возвращает false, могут быть возвращены как null. Данные могут быть возвращены в виде указателя на IndexTuple
, хранящегося в scan->xs_itup
, с дескриптором кортежа scan->xs_itupdesc
; или в виде указателя на HeapTuple
, хранящегося в scan->xs_hitup
, с дескриптором кортежа scan->xs_hitupdesc
. (Последний формат следует использовать при восстановлении данных, которые возможно не помещаются в IndexTuple
). В любом случае управление данными, на которые ссылается указатель, является ответственностью метода доступа. Данные должны оставаться валидными по крайней мере до следующего вызова amgettuple
, amrescan
или amendscan
для данного сканирования.
Функция amgettuple
должна быть предоставлена только в том случае, если метод доступа поддерживает сканирование индекса “plain”. Если это не так, поле amgettuple
в структуре IndexAmRoutine
должно быть установлено в NULL.
int64 amgetbitmap (IndexScanDesc scan, TIDBitmap *tbm);
Извлекает все кортежи из данного сканирования и добавляет их в предоставленный вызывающей стороне TIDBitmap
(то есть объединяет множество идентификаторов кортежей с уже существующим множеством в битовой карте). Возвращается количество извлеченных кортежей (это может быть только приблизительное количество, например, некоторые AM не обнаруживают дубликаты). При вставке идентификаторов кортежей в битовую карту amgetbitmap
может указать, что требуется повторная проверка условий сканирования для конкретных идентификаторов кортежей. Это аналогично выходному параметру xs_recheck
функции amgettuple
. Примечание: в текущей реализации поддержка этой функции смешана с поддержкой потерь при хранении самой битовой карты, и поэтому вызывающие стороны повторно проверяют как условия сканирования, так и частичный предикат индекса (если есть) для повторно проверяемых кортежей. Однако это может быть не всегда верно. amgetbitmap
и amgettuple
не могут использоваться в одном и том же сканировании индекса; также есть и другие ограничения при использовании amgetbitmap
, как объясняется в разделе Раздел 61.3.
Функция amgetbitmap
должна быть предоставлена только в том случае, если метод доступа поддерживает сканирование индекса “bitmap”. Если это не так, поле amgetbitmap
в структуре IndexAmRoutine
должно быть установлено в NULL.
void amendscan (IndexScanDesc scan);
Завершает сканирование и освобождает ресурсы. Структура scan
сама по себе не должна быть освобождена, но все блокировки или фиксации, выполненные внутренне методом доступа, должны быть освобождены, а также любая другая память, выделенная функцией ambeginscan
и другими функциями, связанными со сканированием.
void ammarkpos (IndexScanDesc scan);
Отметьте текущую позицию сканирования. Метод доступа должен поддерживать только одну запомненную позицию сканирования для каждого сканирования.
Функция ammarkpos
нужна только в том случае, если метод доступа поддерживает упорядоченные сканирования. Если это не так, поле ammarkpos
в структуре IndexAmRoutine
может быть установлено в NULL.
void amrestrpos (IndexScanDesc scan);
Восстановите сканирование до самой последней отмеченной позиции.
Функция amrestrpos
нужна только в том случае, если метод доступа поддерживает упорядоченные сканирования. Если это не так, поле amrestrpos
в структуре IndexAmRoutine
может быть установлено в NULL.
В дополнение к поддержке обычных сканирований индексов, некоторые типы индексов могут желать поддержки параллельных сканирований индексов, которые позволяют нескольким процессам сотрудничать при выполнении сканирования индекса. Метод доступа к индексу должен организовать так, чтобы каждый сотрудничающий процесс возвращал подмножество кортежей, которые были бы возвращены обычным, непараллельным сканированием индекса, но таким образом, чтобы объединение этих подмножеств было равно множеству кортежей, которые были бы возвращены обычным, непараллельным сканированием индекса. Кроме того, хотя не требуется, чтобы существовало какое-либо глобальное упорядочение кортежей, возвращаемых параллельным сканированием, упорядочение этого подмножества кортежей, возвращаемых в каждом сотрудничающем процессе, должно соответствовать запрошенному упорядочению. Для поддержки параллельных сканирований индексов могут быть реализованы следующие функции:
Size amestimateparallelscan (void);
Оцените и верните количество байтов динамической общей памяти, которое будет необходимо для выполнения параллельного сканирования доступным методом. (Это число добавляется к, а не заменяет, объем памяти, необходимый для AM-независимых данных в ParallelIndexScanDescData
).
Необходимость реализации этой функции отсутствует для методов доступа, которые не поддерживают параллельные сканирования или для которых количество дополнительных байтов хранилища равно нулю.
void aminitparallelscan (void *target);
Эта функция будет вызвана для инициализации динамической общей памяти в начале параллельного сканирования. target
будет указывать, по крайней мере, на количество байтов, ранее возвращенных amestimateparallelscan
, и эта функция может использовать это количество памяти для хранения любых данных, которые ей необходимы.
Необходимость реализации этой функции отсутствует для методов доступа, которые не поддерживают параллельные сканирования или в случаях, когда общее пространство общей памяти не требует инициализации.
void amparallelrescan (IndexScanDesc scan);
Эта функция, если реализована, будет вызываться, когда необходимо перезапустить параллельное сканирование индекса. Она должна сбросить любое общее состояние, установленное с помощью функции aminitparallelscan
, чтобы сканирование началось сначала.