64.1. Базовая структура API для индексов
Каждый индексный метод доступа описывается строкой в системном каталоге pg_am
. В записи pg_am
указывается имя и функция-обработчик для этого метода. Эти записи могут создаваться и удаляться командами SQL CREATE ACCESS METHOD и DROP ACCESS METHOD.
Функция-обработчик индексного метода доступа должна объявляться как принимающая один аргумент типа internal
и возвращающая псевдотип index_am_handler
. Аргумент в данном случае фиктивный, и нужен только для того, чтобы эту функцию нельзя было вызывать непосредственно из команд SQL. Возвращать эта функция должна структуру типа IndexAmRoutine
(в памяти palloc), содержащую всё, что нужно знать коду ядра, чтобы использовать этот метод доступа. Структура IndexAmRoutine
, также называемая структурой API метода доступа, содержит поля, задающие разнообразные предопределённые свойства метода доступа, например, поддерживает ли он составные индексы. Что более важно, она содержит указатели на опорные функции для метода доступа. Это обычные функции на C и они не видны и не могут быть вызваны на уровне SQL. Опорные функции описаны в Разделе 64.2.
Структура IndexAmRoutine
определяется так:
typedef struct IndexAmRoutine { NodeTag type; /* * Общее число стратегий (операторов), с которыми возможен поиск/применение * этого метода доступа (МД). Ноль, если у этого МД нет фиксированного набора * назначенных стратегий. */ uint16 amstrategies; /* общее число опорных функций, используемых этим МД */ uint16 amsupport; /* номер опорной функции options либо 0 */ uint16 amoptsprocnum; /* поддерживает ли МД упорядочивание (ORDER BY) значений индексированного столбца? */ bool amcanorder; /* поддерживает ли МД упорядочивание (ORDER BY) результата оператора с индексированным столбцом? */ bool amcanorderbyop; /* поддерживает ли МД сканирование в обратном направлении? */ bool amcanbackward; /* поддерживает ли МД уникальные индексы (UNIQUE)? */ bool amcanunique; /* поддерживает ли МД индексы с несколькими столбцами? */ bool amcanmulticol; /* требуется ли для сканирования с МД ограничение первого столбца индекса? */ bool amoptionalkey; /* воспринимает ли МД условия ScalarArrayOpExpr? */ bool amsearcharray; /* воспринимает ли МД условия IS NULL/IS NOT NULL? */ bool amsearchnulls; /* может ли тип, хранящийся в индексе, отличаться от типа столбца? */ bool amstorage; /* возможна ли кластеризация по индексу этого типа? */ bool amclusterable; /* обрабатывает ли МД предикатные блокировки? */ bool ampredlocks; /* поддерживает ли МД параллельное сканирование? */ bool amcanparallel; /* поддерживает ли МД неключевые столбцы, добавляемые указанием INCLUDE? */ bool amcaninclude; /* использует ли МД maintenance_work_mem? */ bool amusemaintenanceworkmem; /* ИЛИ флаги параллельной очистки */ uint8 amparallelvacuumoptions; /* тип данных, хранящихся в индексе, либо InvalidOid, если он переменный */ Oid amkeytype; /* интерфейсные функции */ ambuild_function ambuild; ambuildempty_function ambuildempty; aminsert_function aminsert; ambulkdelete_function ambulkdelete; amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* может быть NULL */ amcostestimate_function amcostestimate; amoptions_function amoptions; amproperty_function amproperty; /* может быть NULL */ ambuildphasename_function ambuildphasename; /* может быть NULL */ amvalidate_function amvalidate; amadjustmembers_function amadjustmembers; /* может быть NULL */ ambeginscan_function ambeginscan; amrescan_function amrescan; amgettuple_function amgettuple; /* может быть NULL */ amgetbitmap_function amgetbitmap; /* может быть NULL */ amendscan_function amendscan; ammarkpos_function ammarkpos; /* может быть NULL */ amrestrpos_function amrestrpos; /* может быть NULL */ /* интерфейсные функции для поддержки параллельного сканирования по индексу */ amestimateparallelscan_function amestimateparallelscan; /* может быть NULL */ aminitparallelscan_function aminitparallelscan; /* может быть NULL */ amparallelrescan_function amparallelrescan; /* может быть NULL */ } IndexAmRoutine;
Чтобы индексный метод доступа применялся, необходимо также определить семейства операторов и классы операторов в pg_opfamily
, pg_opclass
, pg_amop
и pg_amproc
. Эти записи позволяют планировщику понять, для каких видов условий запросов могут применяться индексы с данными методом доступа. Семейства и классы операторов описываются в Разделе 38.16; этот материал необходимо изучить, прежде чем читать данную главу.
Отдельный индекс определяется записью в pg_class
, описывающей его как физическое отношение, и записью в pg_index
, представляющей логическое содержание индекса — то есть, набор столбцов индекса и семантическое значение этих столбцов, установленное соответствующими классами операторов. Столбцами индекса (значениями ключа) могут быть либо простые столбцы нижележащей таблицы, либо выражения, вычисляемые по строкам таблицы. Для индексного метода доступа обычно не важно, откуда поступают значения ключа индекса (они всегда поступают в вычисленном виде), но очень важна информация о классе операторов в каталоге pg_index
. Обе эти записи каталогов представлены в составе структуры данных Relation
, которая передаётся всем функциям, реализующим операции с индексом.
С некоторыми полями флагов в IndexAmRoutine
связаны неочевидные следствия. Требования индексов с amcanunique
описаны в Разделе 64.5. Флаг amcanmulticol
показывает, что метод доступа поддерживает составные индексы, а amoptionalkey
обозначает, что метод позволяет выполнить сканирование при отсутствии индексируемого ограничивающего условия для первого столбца индекса. Когда amcanmulticol
равен false, amoptionalkey
по сути говорит, поддерживает ли метод доступа полное сканирование по индексу без ограничивающего условия. Методы доступа, поддерживающие индексы с несколькими ключевыми столбцами, должны поддерживать сканирования при отсутствии ограничений любых или всех столбцов после первого; однако они могут требовать присутствия какого-либо ограничения для первого столбца индекса, и это требование отмечается значением false флага amoptionalkey
. В amoptionalkey
для метода доступа может устанавливаться false, например, когда этот метод доступа не индексирует значения. Так как большинство индексируемых операторов — строгие, и поэтому не могут вернуть true для операндов NULL, на первый взгляд кажется заманчивой идея не хранить записи индекса для значений NULL: они всё равно никак не могут быть прочитаны при сканировании индекса. Однако этот аргумент отпадает, когда при сканировании индекса вовсе отсутствует ограничение данного столбца индекса. На практике это означает, что индексы с установленным флагом amoptionalkey
должны индексировать значения NULL, так как планировщик может склониться к использованию этого индекса вообще без ключей. С этим связано ещё одно ограничение — индексный метод доступа, поддерживающий составные индексы, должен поддерживать индексирование значений NULL в столбцах после первого, так как планировщик будет полагать, что индекс можно применять для запросов, в которых эти столбцы не ограничиваются. Например, рассмотрим индекс по (a,b) и запрос с ограничением WHERE a = 4
. Система будет полагать, что по этому индексу можно просканировать строки с a = 4
, но это будет неверно, если индекс исключит строки, в которых b
— NULL. Однако этот индекс вполне может исключить строки, в которых первый столбец содержит NULL. Метод индекса, который индексирует значения NULL, может также установить флаг amsearchnulls
, отметив тем самым, что он поддерживает в качестве условий поиска IS NULL
и IS NOT NULL
.
Флаг amcaninclude
показывает, поддерживает ли метод доступа «неключевые» столбцы, то есть может ли он сохранить (без обработки) дополнительные столбцы помимо ключевых. Требования в предыдущем абзаце распространяются только на ключевые столбцы. В частности, сочетание amcanmulticol
=false
и amcaninclude
=true
вполне осмысленно: оно означает, что в индексе может быть только один ключевой столбец и при этом несколько дополнительных неключевых столбцов. Кроме того, неключевые столбцы должны допускать значение null вне зависимости от флага amoptionalkey
.