65.3. Расширяемость
SP-GiST предлагает интерфейс с высоким уровнем абстракции и таким образом требует от разработчика метода доступа реализовать только методы, специфичные для конкретного типа данных. Ядро SP-GiST отвечает за эффективную схему обращений к диску и поиск в структуре дерева, а также берёт на себя заботу о параллельном доступе и поддержке журнала.
Кортежи в листьях дерева SP-GiST содержат значения того же типа данных, что и индексируемый столбец. На верхнем уровне эти кортежи содержат всегда исходное индексируемое значение данных, но на более нижних могут содержать только сокращённое представление, например, суффикс. В этом случае опорные функции класса операторов должны уметь восстанавливать исходное значение, собирая его из внутренних кортежей, которые нужно пройти для достижения уровня конкретного листа.
Внутренние кортежи устроены сложнее, так как они представляют собой точки разветвления в дереве поиска. Каждый внутренний кортеж содержит набор из одного или нескольких узлов, представляющих группы сходных значений листьев. Узел содержит ответвление, приводящее либо к другому, внутреннему кортежу нижнего уровня, либо к короткому списку кортежей в листьях, лежащих в одной странице индекса. Для каждого узла обычно задаётся метка, описывающая его; например, в префиксном дереве меткой может быть очередной символ в строковом значении. (С другой стороны, класс операторов может опускать метки узлов, если он имеет дело с фиксированным набором узлов во всех внутренних кортежах; см. Подраздел 65.4.2.) Дополнительно внутренний кортеж может хранить префикс, описывающий все его члены. В префиксном дереве это может быть общий префикс всех представленных ниже строк. Значением префикса не обязательно должен быть префикс, а могут быть любые данные, требующиеся классу операторов; например, в дереве квадрантов это может быть центральная точка, от которой отмеряются четыре квадранта. В этом случае внутренний кортеж дерева квадрантов будет также содержать четыре узла, соответствующие квадрантам вокруг этой центральной точки.
Некоторые алгоритмы деревьев требует знания уровня (или глубины) текущего кортежа, так что ядро SP-GiST даёт возможность классам операторов контролировать число уровней при спуске по дереву. Также имеется поддержка пошагового восстановления представленного значения, когда это требуется, и передачи вниз дополнительных данных (так называемых переходящих значений) при спуске.
Примечание
Ядро SP-GiST берёт на себя заботу о значениях NULL. Хотя в индексах SP-GiST не хранятся записи для NULL в индексируемых столбцах, это скрыто от кода класса операторов; записи индексов или условия поиска с NULL никогда не передаются методам класса операторов. (Предполагается, что операторы SP-GiST строгие и не могут возвращать положительный результат для значений NULL.) Поэтому значения NULL здесь больше обсуждаться не будут.
Класс операторов индекса для SP-GiST должен предоставить пять методов и может дополнительно предоставить ещё один. Все пять обязательных методов должны по единому соглашению принимать два аргумента internal
, первым из которых будет указатель на структуру C, содержащую входные значения для опорного метода, а вторым — указатель на структуру C, в которую должны помещаться выходные значения. Четыре из этих методов должны возвращать просто void
, так как их результаты помещаются в выходную структуру; однако leaf_consistent
дополнительно возвращает результат boolean
. Эти методы не должны менять никакие поля в их входных структурах. Выходная структура всегда обнуляется перед вызовом пользовательского метода. Необязательный шестой метод compress
принимает в единственном аргументе данные, подлежащие индексированию, и возвращает значение, подходящее для физического хранения в кортеже уровня листьев.
Пользователь должен определить следующие пять обязательных методов:
config
Возвращает статическую информацию о реализации индекса, включая OID типов данных префикса и метки узла.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_config(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgConfigIn
языка C, содержащие входные данные для функции. Во втором аргументе передаётся указатель на структуруspgConfigOut
языка C, в которую функция должна поместить результат.typedef struct spgConfigIn { Oid attType; /* Индексируемый тип данных */ } spgConfigIn; typedef struct spgConfigOut { Oid prefixType; /* Тип данных префикса во внутренних кортежах */ Oid labelType; /* Тип данных метки узла во внутренних кортежах */ Oid leafType; /* Тип данных в кортежах уровня листьев */ bool canReturnData; /* Класс операторов может восстановить исходные данные */ bool longValuesOK; /* Класс может принимать значения, не умещающиеся на одной странице */ } spgConfigOut;
Поле
attType
передаётся для поддержки полиморфных классов операторов; для обычных классов операторов с фиксированным типом оно будет всегда содержать одно значение и поэтому его можно просто игнорировать.Для классов операторов, не использующих префиксы, в
prefixType
можно установитьVOIDOID
. Подобным образом, для классов операторов, не использующих метки узлов, вlabelType
тоже можно установитьVOIDOID
. ПризнакcanReturnData
следует установить, если класс операторов может восстановить изначально переданное в индекс значение. ПризнакlongValuesOK
должен устанавливаться, только еслиattType
переменной длины и класс операторов может фрагментировать длинные значения, повторяя суффиксы (см. Подраздел 65.4.1).Значение
leafType
обычно совпадает сattType
. Для обеспечения обратной совместимости методуconfig
разрешается оставитьleafType
неинициализированным; это будет иметь тот же эффект, что и присваиваниеleafType
значенияattType
. КогдаattType
иleafType
различаются, должен предоставляться методcompress
. Методcompress
отвечает за преобразование данных, подлежащих индексации, из типаattType
в типleafType
. Заметьте, что обе функции, оценивающие согласованность, получают значенияscankeys
неизменёнными, не прошедшими черезcompress
.choose
Выбирает метод для добавления нового значения во внутренний кортеж.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_choose(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgChooseIn
языка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgChooseOut
, в которую функция должна поместить результат.typedef struct spgChooseIn { Datum datum; /* исходное значение, которое должно индексироваться */ Datum leafDatum; /* текущее значение, которое должно сохраниться в листе */ int level; /* текущий уровень (начиная с нуля) */ /* Данные из текущего внутреннего кортежа */ bool allTheSame; /* кортеж с признаком все-равны? */ bool hasPrefix; /* у кортежа есть префикс? */ Datum prefixDatum; /* если да, то это значение префикса */ int nNodes; /* число узлов во внутреннем кортеже */ Datum *nodeLabels; /* значения меток узлов (NULL, если их нет) */ } spgChooseIn; typedef enum spgChooseResultType { spgMatchNode = 1, /* спуститься в существующий узел */ spgAddNode, /* добавить узел во внутренний кортеж */ spgSplitTuple /* разделить внутренний кортеж (изменить его префикс) */ } spgChooseResultType; typedef struct spgChooseOut { spgChooseResultType resultType; /* код действия, см. выше */ union { struct /* результаты для spgMatchNode */ { int nodeN; /* спуститься к этому узлу (нумерация с 0) */ int levelAdd; /* шаг увеличения уровня */ Datum restDatum; /* новое значение листа */ } matchNode; struct /* результаты для spgAddNode */ { Datum nodeLabel; /* метка нового узла */ int nodeN; /* куда вставлять её (нумерация с 0) */ } addNode; struct /* результаты для spgSplitTuple */ { /* Информация для формирования нового внутреннего кортежа верхнего уровня с одним дочерним кортежем */ bool prefixHasPrefix; /* кортеж должен иметь префикс? */ Datum prefixPrefixDatum; /* если да, его значение */ int prefixNNodes; /* число узлов */ Datum *prefixNodeLabels; /* их метки (или NULL, если * меток нет) */ int childNodeN; /* узел, который получит дочерний кортеж */ /* Информация для формирования нового внутреннего кортежа нижнего уровня со всеми старыми узлами */ bool postfixHasPrefix; /* кортеж должен иметь префикс? */ Datum postfixPrefixDatum; /* если да, его значение */ } splitTuple; } result; } spgChooseOut;
В
datum
передаётся исходное значение типаspgConfigIn
.attType
, которое должно быть вставлено в индекс. ВleafDatum
содержится значение типаspgConfigOut
.leafType
, изначально представляющее собой результат методаcompress
, применённого кdatum
, если методcompress
реализован, а иначе — собственно значениеdatum
.leafDatum
может быть другим на нижних уровнях дерева, если его изменят функцииchoose
илиpicksplit
. Когда поиск места добавления достигает страницы уровня листа, в создаваемом кортеже листа сохраняется текущее значениеleafDatum
. Вlevel
задаётся текущий уровень внутреннего кортежа, начиная с нуля для уровня корня. ПризнакallTheSame
устанавливается, если текущий внутренний кортеж содержит несколько равнозначных узлов (см. Подраздел 65.4.3). ПризнакhasPrefix
устанавливается, если текущий внутренний кортеж содержит префикс; в этом случае вprefixDatum
задаётся его значение. ПолеnNodes
задаёт число дочерних узлов, содержащихся во внутреннем кортеже, аnodeLabels
представляет массив их меток или NULL, если меток у них нет.Функция
choose
может определить, соответствует ли новое значение одному из существующих дочерних узлов, или что нужно добавить новый дочерний узел, или что новое значение не согласуется с префиксом кортежа и внутренний кортеж нужно разделить, чтобы получить менее ограничивающий префикс.Если новое значение соответствует одному из существующих дочерних узлов, установите в
resultType
значениеspgMatchNode
. Установите вnodeN
номер этого узла в массиве узлов (нумерация начинается с нуля). Установите вlevelAdd
значение, на которое должен увеличиваться уровень (level
) при спуске через этот узел, либо оставьте его нулевым, если класс операторов не отслеживает уровни. УстановитеrestDatum
, равнымleafDatum
, если класс операторов не меняет значения данных от уровня к уровню, а в противном случае запишите в него изменённое значение, которое должно использоваться в качествеleafDatum
на следующем уровне.Если нужно добавить новый дочерний узел, установите в
resultType
значениеspgAddNode
. ВnodeLabel
задайте метку для нового узла, а вnodeN
позицию (отсчитываемую от нуля), в которую должен вставляться узел в массиве узлов. После того как узел будет добавлен, функцияchoose
вызывается снова с изменённым внутренним кортежем; в результате этого вызова должен быть получен результатspgMatchNode
.Если новое значение не согласуется с префиксом кортежа, установите в
resultType
значениеspgSplitTuple
. Это действие приводит к перемещению всех существующих узлов в новый внутренний кортеж нижнего уровня и замене существующего внутреннего кортежа кортежем, содержащим одну ссылку вниз на новый внутренний кортеж. Установите признакprefixHasPrefix
, чтобы указать, должен ли новый верхний кортеж иметь префикс, и если да, задайте вprefixPrefixDatum
значение префикса. Это новое значение префикса должно быть в достаточной мере менее ограничивающим, чем исходное, чтобы в индекс было принято новое значение. Запишите вprefixNNodes
число требующихся узлов в новом кортеже, а вprefixNodeLabels
— указатель на выделенный через palloc массив с их метками или NULL, если метки узлов не нужны. Заметьте, что общий размер нового кортежа верхнего уровня не должен превышать общий размер кортежа, который он замещает; это ограничивает длины нового префикса и новых меток. Установите вchildNodeN
индекс (начиная с нуля) узла, который будет ссылаться на новый внутренний кортеж нижнего уровня. Установите признакpostfixHasPrefix
, чтобы указать, должен ли новый внутренний кортеж нижнего уровня иметь префикс, и если да, задайте вpostfixPrefixDatum
значение префикса. Сочетание этих двух префиксов и метки узла, ссылающегося вниз, (если она есть) должно иметь то же значение, что и исходный префикс, так как нет возможности ни изменить метки узлов, перемещённых в новый кортеж нижнего уровня, ни изменить какие-либо нижние записи индекса. После того как узел разделён, функцияchoose
будет вызвана снова с заменяемым внутренним кортежем. При этом вызове может быть возвращён результатspgAddNode
, если подходящий узел не был создан действиемspgSplitTuple
. В конце концовchoose
должна вернутьspgMatchNode
, чтобы операция добавления могла перейти на следующий уровень.picksplit
Выбирает, как создать новый внутренний кортеж по набору кортежей в листьях.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_picksplit(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgPickSplitIn
языка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgPickSplitOut
языка C, в которую функция должна поместить результат.typedef struct spgPickSplitIn { int nTuples; /* число кортежей в листьях */ Datum *datums; /* их значения (массив длины nTuples) */ int level; /* текущий уровень (отсчитывая от 0) */ } spgPickSplitIn; typedef struct spgPickSplitOut { bool hasPrefix; /* новый внутренний кортеж должен иметь префикс? */ Datum prefixDatum; /* если да, его значение */ int nNodes; /* число узлов для нового внутреннего кортежа */ Datum *nodeLabels; /* их метки (или NULL, если их нет) */ int *mapTuplesToNodes; /* номер узла для каждого кортежа в листе */ Datum *leafTupleDatums; /* значения, помещаемые в каждый новый кортеж */ } spgPickSplitOut;
В
nTuples
задаётся число предоставленных кортежей уровня листьев, а вdatums
— массив их значений типаspgConfigOut
.leafType
. Вlevel
указывается текущий уровень, который должны разделять все кортежи листьев, и который станет уровнем нового внутреннего кортежа.Установите признак
hasPrefix
, чтобы указать, должен ли новый внутренний кортеж иметь префикс, и если да, задайте вprefixDatum
значение префикса. Установите вnNodes
количество узлов, которые будут содержаться во внутреннем кортеже, а вnodeLabels
— массив значений их меток либо NULL, если узлам не нужны метки. Поместите вmapTuplesToNodes
указатель на массив, назначающий номера узлов (начиная с нуля) каждому кортежу листа. ВleafTupleDatums
передайте массив значений, которые должны быть сохранены в новых кортежах листьев (они будут совпадать со входными значениями (datums
), если класс операторов не изменяет значения от уровня к следующему). Заметьте, что функцияpicksplit
сама должна выделить память, используя palloc, для массивовnodeLabels
,mapTuplesToNodes
иleafTupleDatums
.Если передаётся несколько кортежей листьев, ожидается, что функция
picksplit
классифицирует их и разделит на несколько узлов; иначе нельзя будет разнести кортежи листьев по разным страницам, что является конечной целью этой операции. Таким образом, еслиpicksplit
в итоге помещает все кортежи листьев в один узел, ядро SP-GiST меняет это решение и создаёт внутренний кортеж, в котором кортежи листьев связываются случайным образом с несколькими узлами с одинаковыми метками. Такой кортеж помечается флагомallTheSame
, показывающим, что все узлы равны. Функцииchoose
иinner_consistent
должны работать с такими внутренними кортежами особым образом. За дополнительными сведениями обратитесь к Подразделу 65.4.3.picksplit
может применяться к одному кортежу на уровне листьев, только когда функцияconfig
установила вlongValuesOK
значение true и было передано входное значение, большее страницы. В этом случае цель операции — отделить префикс и получить новое, более короткое значение для листа. Этот вызов будет повторяться, пока значение уровня листа не уменьшится настолько, чтобы уместиться в странице. За дополнительными сведениями обратитесь к Подразделу 65.4.1.inner_consistent
Возвращает набор узлов (ветвей), по которым надо продолжать поиск.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgInnerConsistentIn
языка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgInnerConsistentOut
языка C, в которую функция должна поместить результат.typedef struct spgInnerConsistentIn { ScanKey scankeys; /* массив операторов и искомых значений */ ScanKey orderbys; /* массив операторов упорядочивания и * сравниваемых значений */ int nkeys; /* длина массива scankeys */ int norderbys; /* длина массива orderbys */ Datum reconstructedValue; /* значение, восстановленное для родителя */ void *traversalValue; /* переходящее значение, специфичное для класса операторов */ MemoryContext traversalMemoryContext; /* переходящие значения нужно помещать сюда */ int level; /* текущий уровень (отсчитывается от нуля) */ bool returnData; /* нужно ли возвращать исходные данные? */ /* Данные из текущего внутреннего кортежа */ bool allTheSame; /* кортеж с признаком все-равны? */ bool hasPrefix; /* у кортежа есть префикс? */ Datum prefixDatum; /* если да, то это значение префикса */ int nNodes; /* число узлов во внутреннем кортеже */ Datum *nodeLabels; /* значения меток узлов (NULL, если их нет) */ } spgInnerConsistentIn; typedef struct spgInnerConsistentOut { int nNodes; /* число дочерних узлов, которые нужно посетить */ int *nodeNumbers; /* их номера в массиве узлов */ int *levelAdds; /* шаги увеличения уровня для этих узлов */ Datum *reconstructedValues; /* связанные восстановленные значения */ void **traversalValues; /* переходящие значения, специфичные для класса операторов */ double **distances; /* связанные расстояния */ } spgInnerConsistentOut;
Массив
scankeys
длиныnkeys
описывает условия поиска по индексу. Эти условия объединяются операцией И — найдены должны быть только те записи, которые удовлетворяют всем условиям. (Заметьте, что сnkeys
= 0 подразумевается, что запросу удовлетворяют все записи в индексе.) Обычно эту функцию интересуют только поляsk_strategy
иsk_argument
в каждой записи массива, в которых определяется соответственно индексируемый оператор и искомое значение. В частности, нет необходимости проверятьsk_flags
, чтобы распознать NULL в искомом значении, так как ядро SP-GiST отфильтрует такие условия. Массивorderbys
длиныnorderbys
подобным образом описывает упорядочивающие операторы (если они есть). ВreconstructedValue
передаётся значение, восстановленное для родительского кортежа; это может быть(Datum) 0
на уровне корня или если функцияinner_consistent
не установила значение на предыдущем уровне. ЗначениеreconstructedValue
всегда имеет типspgConfigOut
.leafType
. ВtraversalValue
передаётся указатель на переходящие данные, полученные из предыдущего вызоваinner_consistent
для родительского кортежа индекса, либо NULL на уровне корня. ПолеtraversalMemoryContext
указывает на контекст памяти, в котором нужно сохранить выходные переходящие данные (см. ниже). Вlevel
передаётся уровень текущего внутреннего кортежа (уровень корня считается нулевым). ФлагreturnData
устанавливается, когда для этого запроса нужно получить восстановленные данные; это возможно, только если функцияconfig
установила признакcanReturnData
. ПризнакallTheSame
устанавливается, если текущий внутренний кортеж имеет пометку «все-равны»; в этом случае все узлы имеют одну метку (если имеют) и значит, либо все они, либо никакой не соответствует запросу (см. Подраздел 65.4.3). ПризнакhasPrefix
устанавливается, если текущий внутренний кортеж содержит префикс; в этом случае вprefixDatum
находится его значение. ВnNodes
задаётся число дочерних узлов, содержащихся во внутреннем кортеже, а вnodeLabels
— массив их меток либо NULL, если они не имеют меток.В
nNodes
нужно записать число дочерних узлов, которые потребуется посетить при поиске, а вnodeNumbers
— массив их индексов. Если класс операторов отслеживает уровни, вlevelAdds
нужно передать массив с шагами увеличения уровня при посещении каждого узла. (Часто шаг будет одним для всех узлов, но может быть и по-другому, поэтому применяется массив.) Если потребовалось восстановить значения, поместите вreconstructedValues
указатель на массив значений типаspgConfigOut
.leafType
, восстановленных для каждого дочернего узла, который нужно посетить; в противном случае оставьтеreconstructedValues
равным NULL. Если выполняется поиск с упорядочиванием, поместите вdistances
массив расстояний в соответствии с массивомorderbys
(узлы с меньшими расстояниями будут обрабатываться первыми). В противном случае оставьте в этом поле NULL. Если желательно передать дополнительные данные («переходящие значения») на нижние уровни при поиске по дереву, поместите вtraversalValues
указатель на массив соответствующих переходящих значений, по одному для каждого дочернего узла, который нужно посетить; в противном случае оставьте вtraversalValues
значение NULL. Заметьте, что функцияinner_consistent
сама должна выделять память, используя palloc, для массивовnodeNumbers
,levelAdds
,distances
,reconstructedValues
иtraversalValues
в текущем контексте памяти. Однако выходные переходящие значения, на которые указывает массивtraversalValues
, должны размещаться в контекстеtraversalMemoryContext
. При этом каждое переходящее значения должно располагаться в отдельном блоке памяти palloc.leaf_consistent
Возвращает true, если кортеж листа удовлетворяет запросу.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...
В первом аргументе передаётся указатель на структуру
spgLeafConsistentIn
языка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgLeafConsistentOut
языка C, в которую функция должна поместить результат.typedef struct spgLeafConsistentIn { ScanKey scankeys; /* массив операторов и искомых значений */ ScanKey orderbys; /* массив операторов упорядочивания и * сравниваемых значений */ int nkeys; /* длина массива scankeys */ int norderbys; /* длина массива orderbys */ Datum reconstructedValue; /* значение, восстановленное для родителя */ void *traversalValue; /* переходящее значение, специфичное для класса операторов */ int level; /* текущий уровень (отсчитывая от нуля) */ bool returnData; /* нужно ли возвращать исходные данные? */ Datum leafDatum; /* значение в кортеже листа */ } spgLeafConsistentIn; typedef struct spgLeafConsistentOut { Datum leafValue; /* восстановленные исходные данные, при наличии */ bool recheck; /* true, если оператор нужно перепроверить */ bool recheckDistances; /* true, если расстояния нужно перепроверить */ double *distances; /* связанные расстояния */ } spgLeafConsistentOut;
Массив
scankeys
длиныnkeys
описывает условия поиска по индексу. Эти условия объединяются операцией И — запросу удовлетворяют только те записи в индексе, которые удовлетворяют всем этим условиям. (Заметьте, что сnkeys
= 0 подразумевается, что запросу удовлетворяют все записи в индексе.) Обычно эту функцию интересуют только поляsk_strategy
иsk_argument
в каждой записи массива, в которых определяются соответственно индексируемый оператор и искомое значение. В частности, нет необходимости проверятьsk_flags
, чтобы распознать NULL в искомом значении, так как ядро SP-GiST отфильтрует такие условия. Массивorderbys
длиныnorderbys
подобным образом описывает упорядочивающие операторы. ВreconstructedValue
передаётся значение, восстановленное для родительского кортежа; это может быть(Datum) 0
на уровне корня или если функцияinner_consistent
не установила значение на предыдущем уровне. ЗначениеreconstructedValue
всегда имеет типspgConfigOut
.leafType
. ВtraversalValue
передаётся указатель на переходящие данные, полученные из предыдущего вызоваinner_consistent
для родительского кортежа индекса, либо NULL на уровне корня. Вlevel
передаётся уровень текущего внутреннего кортежа (уровень корня считается нулевым). ФлагreturnData
устанавливается, когда для этого запроса нужно получить восстановленные данные; это возможно, только если функцияconfig
установила признакcanReturnData
. ВleafDatum
передаётся значение ключа, записанное в текущем кортеже листа.Эта функция должна вернуть
true
, если кортеж листа соответствует запросу, илиfalse
в противном случае. В случае положительного результата, если в полеreturnData
переданоtrue
, нужно поместить вleafValue
значение типаspgConfigIn
.attType
, изначально переданное для индексации в этот кортеж. Кроме того, флагуrecheck
можно присвоитьtrue
, если соответствие неточное, так что для установления точного результата проверки нужно повторно применить оператор(ы) к собственно кортежу данных. Если выполняется упорядочивающий поиск, поместите вdistances
массив со значениями расстояния, соответствующими массивуorderbys
. В противном случае оставьте в этом поле NULL. Если хотя бы одно из возвращаемых расстояний определено неточно, присвойте true полюrecheckDistances
. В этом случае исполнитель вычислит точные расстояния после получения кортежа из кучи и переупорядочит кортежи, если потребуется.
Дополнительно пользователь может определить метод:
Datum compress(Datum in)
Преобразует элемент данных в формат, подходящий для физического хранения в кортеже уровня листьев на странице индекса. Эта функция принимает значение
spgConfigIn
.attType
и возвращаетspgConfigOut
.leafType
(это значение должно быть не в виде TOAST).
Все опорные методы SP-GiST обычно вызываются в кратковременных контекстах памяти; то есть CurrentMemoryContext
сбрасывается после обработки каждого кортежа. Таким образом, можно не заботиться об освобождении любых блоков памяти, выделенных функцией palloc. (Метод config
является исключением: в нём нужно не допускать утечек памяти. Но обычно метод config
не делает ничего, кроме как присваивает константы переданной структуре параметров.)
Если индексируемый столбец имеет сортируемый тип данных, правило сортировки индекса будет передаваться всем опорным методам, используя стандартный механизм PG_GET_COLLATION()
.
51.74. pg_locks
The view pg_locks
provides access to information about the locks held by active processes within the database server. See Chapter 13 for more discussion of locking.
pg_locks
contains one row per active lockable object, requested lock mode, and relevant process. Thus, the same lockable object might appear many times, if multiple processes are holding or waiting for locks on it. However, an object that currently has no locks on it will not appear at all.
There are several distinct types of lockable objects: whole relations (e.g., tables), individual pages of relations, individual tuples of relations, transaction IDs (both virtual and permanent IDs), and general database objects (identified by class OID and object OID, in the same way as in pg_description
or pg_depend
). Also, the right to extend a relation is represented as a separate lockable object, as is the right to update pg_database
.datfrozenxid
. Also, “advisory” locks can be taken on numbers that have user-defined meanings.
Table 51.75. pg_locks
Columns
Name | Type | References | Description |
---|---|---|---|
locktype | text | Type of the lockable object: relation , extend , frozenid , page , tuple , transactionid , virtualxid , object , userlock , or advisory | |
database | oid |
| OID of the database in which the lock target exists, or zero if the target is a shared object, or null if the target is a transaction ID |
relation | oid |
| OID of the relation targeted by the lock, or null if the target is not a relation or part of a relation |
page | integer | Page number targeted by the lock within the relation, or null if the target is not a relation page or tuple | |
tuple | smallint | Tuple number targeted by the lock within the page, or null if the target is not a tuple | |
virtualxid | text | Virtual ID of the transaction targeted by the lock, or null if the target is not a virtual transaction ID | |
transactionid | xid | ID of the transaction targeted by the lock, or null if the target is not a transaction ID | |
classid | oid |
| OID of the system catalog containing the lock target, or null if the target is not a general database object |
objid | oid | any OID column | OID of the lock target within its system catalog, or null if the target is not a general database object |
objsubid | smallint | Column number targeted by the lock (the classid and objid refer to the table itself), or zero if the target is some other general database object, or null if the target is not a general database object | |
virtualtransaction | text | Virtual ID of the transaction that is holding or awaiting this lock | |
pid | integer | Process ID of the server process holding or awaiting this lock, or null if the lock is held by a prepared transaction | |
mode | text | Name of the lock mode held or desired by this process (see Section 13.3.1 and Section 13.2.3) | |
granted | boolean | True if lock is held, false if lock is awaited | |
fastpath | boolean | True if lock was taken via fast path, false if taken via main lock table |
granted
is true in a row representing a lock held by the indicated process. False indicates that this process is currently waiting to acquire this lock, which implies that at least one other process is holding or waiting for a conflicting lock mode on the same lockable object. The waiting process will sleep until the other lock is released (or a deadlock situation is detected). A single process can be waiting to acquire at most one lock at a time.
Throughout running a transaction, a server process holds an exclusive lock on the transaction's virtual transaction ID. If a permanent ID is assigned to the transaction (which normally happens only if the transaction changes the state of the database), it also holds an exclusive lock on the transaction's permanent transaction ID until it ends. When a process finds it necessary to wait specifically for another transaction to end, it does so by attempting to acquire share lock on the other transaction's ID (either virtual or permanent ID depending on the situation). That will succeed only when the other transaction terminates and releases its locks.
Although tuples are a lockable type of object, information about row-level locks is stored on disk, not in memory, and therefore row-level locks normally do not appear in this view. If a process is waiting for a row-level lock, it will usually appear in the view as waiting for the permanent transaction ID of the current holder of that row lock.
Advisory locks can be acquired on keys consisting of either a single bigint
value or two integer values. A bigint
key is displayed with its high-order half in the classid
column, its low-order half in the objid
column, and objsubid
equal to 1. The original bigint
value can be reassembled with the expression (classid::bigint << 32) | objid::bigint
. Integer keys are displayed with the first key in the classid
column, the second key in the objid
column, and objsubid
equal to 2. The actual meaning of the keys is up to the user. Advisory locks are local to each database, so the database
column is meaningful for an advisory lock.
pg_locks
provides a global view of all locks in the database cluster, not only those relevant to the current database. Although its relation
column can be joined against pg_class
.oid
to identify locked relations, this will only work correctly for relations in the current database (those for which the database
column is either the current database's OID or zero).
The pid
column can be joined to the pid
column of the pg_stat_activity
view to get more information on the session holding or awaiting each lock, for example
SELECT * FROM pg_locks pl LEFT JOIN pg_stat_activity psa ON pl.pid = psa.pid;
Also, if you are using prepared transactions, the virtualtransaction
column can be joined to the transaction
column of the pg_prepared_xacts
view to get more information on prepared transactions that hold locks. (A prepared transaction can never be waiting for a lock, but it continues to hold the locks it acquired while running.) For example:
SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx ON pl.virtualtransaction = '-1/' || ppx.transaction;
While it is possible to obtain information about which processes block which other processes by joining pg_locks
against itself, this is very difficult to get right in detail. Such a query would have to encode knowledge about which lock modes conflict with which others. Worse, the pg_locks
view does not expose information about which processes are ahead of which others in lock wait queues, nor information about which processes are parallel workers running on behalf of which other client sessions. It is better to use the pg_blocking_pids()
function (see Table 9.63) to identify which process(es) a waiting process is blocked behind.
The pg_locks
view displays data from both the regular lock manager and the predicate lock manager, which are separate systems; in addition, the regular lock manager subdivides its locks into regular and fast-path locks. This data is not guaranteed to be entirely consistent. When the view is queried, data on fast-path locks (with fastpath
= true
) is gathered from each backend one at a time, without freezing the state of the entire lock manager, so it is possible for locks to be taken or released while information is gathered. Note, however, that these locks are known not to conflict with any other lock currently in place. After all backends have been queried for fast-path locks, the remainder of the regular lock manager is locked as a unit, and a consistent snapshot of all remaining locks is collected as an atomic action. After unlocking the regular lock manager, the predicate lock manager is similarly locked and all predicate locks are collected as an atomic action. Thus, with the exception of fast-path locks, each lock manager will deliver a consistent set of results, but as we do not lock both lock managers simultaneously, it is possible for locks to be taken or released after we interrogate the regular lock manager and before we interrogate the predicate lock manager.
Locking the regular and/or predicate lock manager could have some impact on database performance if this view is very frequently accessed. The locks are held only for the minimum amount of time necessary to obtain data from the lock managers, but this does not completely eliminate the possibility of a performance impact.