2025年9月25日: PostgreSQL 18 釋出!
支援的版本:當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本:開發版
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2

65.3。SP-GiST 索引 #

65.3.1。簡介 #

SP-GiST是空間分割槽的縮寫。GiST. SP-GiST支援分割槽搜尋樹,這有助於開發各種不同的非平衡資料結構,例如四叉樹、k-d 樹和基數樹(trie 樹)。這些結構的共同特點是它們反覆將搜尋空間劃分為不需要大小相等的子空間。與分割槽規則匹配良好的搜尋可以非常快速。

這些流行的資料結構最初是為記憶體使用而開發的。在主記憶體中,它們通常被設計為一組透過指標連結的動態分配節點。這不適合直接儲存到磁碟,因為這些指標鏈可能相當長,這將需要太多的磁碟訪問。相反,基於磁碟的資料結構應該具有高扇出以最小化 I/O。解決的挑戰是SP-GiST是將搜尋樹節點對映到磁碟頁面,使得搜尋只需要訪問幾個磁碟頁面,即使它遍歷許多節點。

GiST, SP-GiST一樣,它旨在允許由資料型別領域的專家(而不是資料庫專家)開發具有適當訪問方法的自定義資料型別。

這裡的一些資訊來自普渡大學的 SP-GiST 索引專案網站。在PostgreSQL中,SP-GiST的實現主要由 Teodor Sigaev 和 Oleg Bartunov 維護,他們的網站上有更多資訊。

65.3.2。內建運算子類 #

核心 PostgreSQL 發行版包含SP-GiST運算子類如表 65.2所示。

表 65.2。內建SP-GiST運算子類

名稱 可索引運算子 排序運算子
box_ops << (box,box) <-> (box,point)
&< (box,box)
&> (box,box)
>> (box,box)
<@ (box,box)
@> (box,box)
~= (box,box)
&& (box,box)
<<| (box,box)
&<| (box,box)
|&> (box,box)
|>> (box,box)
inet_ops << (inet,inet)  
<<= (inet,inet)
>> (inet,inet)
>>= (inet,inet)
= (inet,inet)
<> (inet,inet)
< (inet,inet)
<= (inet,inet)
> (inet,inet)
>= (inet,inet)
&& (inet,inet)
kd_point_ops |>> (point,point) <-> (point,point)
<< (point,point)
>> (point,point)
<<| (point,point)
~= (point,point)
<@ (point,box)
poly_ops << (polygon,polygon) <-> (polygon,point)
&< (polygon,polygon)
&> (polygon,polygon)
>> (polygon,polygon)
<@ (polygon,polygon)
@> (polygon,polygon)
~= (polygon,polygon)
&& (polygon,polygon)
<<| (polygon,polygon)
&<| (polygon,polygon)
|>> (polygon,polygon)
|&> (polygon,polygon)
quad_point_ops |>> (point,point) <-> (point,point)
<< (point,point)
>> (point,point)
<<| (point,point)
~= (point,point)
<@ (point,box)
range_ops = (anyrange,anyrange)  
&& (anyrange,anyrange)
@> (anyrange,anyelement)
@> (anyrange,anyrange)
<@ (anyrange,anyrange)
<< (anyrange,anyrange)
>> (anyrange,anyrange)
&< (anyrange,anyrange)
&> (anyrange,anyrange)
-|- (anyrange,anyrange)
text_ops = (text,text)  
< (text,text)
<= (text,text)
> (text,text)
>= (text,text)
~<~ (text,text)
~<=~ (text,text)
~>=~ (text,text)
~>~ (text,text)
^@ (text,text)

對於point型別的兩個運算子類中,quad_point_ops是預設的。kd_point_ops支援相同的運算子,但使用不同的索引資料結構,這在某些應用中可能提供更好的效能。

quad_point_opskd_point_opspoly_ops運算子類支援<->排序運算子,該運算子可以在索引點或多邊形資料集上啟用 k-最近鄰(k-NN)搜尋。

65.3.3。可擴充套件性 #

SP-GiST提供了一個高度抽象的介面,要求訪問方法開發人員只實現特定於給定資料型別的方法。SP-GiST核心負責高效的磁碟對映和樹結構的搜尋。它還負責併發和日誌記錄方面的問題。

一個SP-GiST樹的葉元組通常包含與索引列相同資料型別的值,儘管它們也可以包含索引列的有損表示。儲存在根級別的葉元組將直接表示原始索引資料值,但較低級別的葉元組可能只包含部分值,例如字尾。在這種情況下,運算子類支援函式必須能夠使用從傳遞到葉級別的內部元組中積累的資訊來重建原始值。

當使用INCLUDE列建立SP-GiST索引時,這些列的值也會儲存在葉元組中。INCLUDE列與SP-GiST運算子類無關,因此此處不再贅述。

內部元組更復雜,因為它們是搜尋樹中的分支點。每個內部元組包含一個或多個節點的集合,這些節點表示相似葉值的組。一個節點包含一個向下連結,該連結要麼指向另一個更低級別的內部元組,要麼指向一個短的葉元組列表,這些葉元組都位於同一個索引頁上。每個節點通常有一個描述它的標籤;例如,在基數樹中,節點標籤可以是字串值的下一個字元。(或者,如果運算子類對所有內部元組都使用固定節點集,則可以省略節點標籤;請參閱第 65.3.4.2 節。)可選地,內部元組可以有一個描述其所有成員的字首值。在基數樹中,這可以是表示字串的共同字首。字首值不一定真的是字首,但可以是運算子類所需的任何資料;例如,在四叉樹中,它可以儲存四個象限相對於其中心點進行度量的中心點。然後,四叉樹內部元組還將包含四個對應於該中心點周圍象限的節點。

有些樹演算法需要了解當前元組的級別(或深度),因此SP-GiST核心程式碼提供了運算子類在遍歷樹時管理級別計數的可能性。還支援在需要時逐步重建表示值,並在樹下降過程中傳遞附加資料(稱為遍歷值)。

注意

SP-GiST核心程式碼處理空條目。儘管SP-GiST索引確實儲存索引列中空值的條目,但這對於索引運算子類程式碼是隱藏的:空索引條目或搜尋條件永遠不會傳遞給運算子類方法。(假設SP-GiST運算子是嚴格的,因此不能成功處理空值。)因此,此處不再討論空值。

一個SP-GiST索引運算子類必須提供五個使用者定義的方法,其中兩個是可選的。所有五個強制方法都遵循接受兩個internal引數的約定,其中第一個是指向 C 結構體的指標,該結構體包含支援方法的輸入值,而第二個引數是指向 C 結構體的指標,輸出值必須放置在該結構體中。四個強制方法只返回void,因為它們的所有結果都出現在輸出結構體中;但是leaf_consistent返回boolean結果。這些方法不得修改其輸入結構體的任何欄位。在所有情況下,輸出結構體在呼叫使用者定義方法之前都初始化為零。可選的第六個方法compress接受一個要索引的datum作為唯一引數,並返回適合在葉元組中進行物理儲存的值。可選的第七個方法options接受一個指向 C 結構體的internal指標,運算子類特定的引數應放置在該結構體中,並返回void

五個強制的使用者定義方法是

config

返回有關索引實現的靜態資訊,包括字首和節點標籤資料型別的資料型別 OID。

SQL函式宣告必須如下所示

CREATE FUNCTION my_config(internal, internal) RETURNS void ...

第一個引數是指向spgConfigIn C 結構體的指標,其中包含函式的輸入資料。第二個引數是指向spgConfigOut C 結構體的指標,函式必須用結果資料填充該結構體。

typedef struct spgConfigIn
{
    Oid         attType;        /* Data type to be indexed */
} spgConfigIn;

typedef struct spgConfigOut
{
    Oid         prefixType;     /* Data type of inner-tuple prefixes */
    Oid         labelType;      /* Data type of inner-tuple node labels */
    Oid         leafType;       /* Data type of leaf-tuple values */
    bool        canReturnData;  /* Opclass can reconstruct original data */
    bool        longValuesOK;   /* Opclass can cope with values > 1 page */
} spgConfigOut;

傳入attType是為了支援多型索引運算子類;對於普通的固定資料型別運算子類,它的值將始終相同,因此可以忽略。

對於不使用字首的運算子類,prefixType可以設定為VOIDOID。同樣,對於不使用節點標籤的運算子類,labelType可以設定為VOIDOIDcanReturnData應設定為 true,如果運算子類能夠重建最初提供的索引值。longValuesOK應僅在attType是變長且運算子類能夠透過重複字尾分割長值時(請參閱第 65.3.4.1 節)設定為 true。

leafType應與運算子類的opckeytype目錄條目定義的索引儲存型別匹配。(請注意,opckeytype可以為零,這意味著儲存型別與運算子類的輸入型別相同,這是最常見的情況。)出於向後相容性原因,config方法可以將leafType設定為其他值,並且將使用該值;但此操作已被棄用,因為索引內容隨後在目錄中被錯誤識別。此外,允許將leafType未初始化(零);這被解釋為表示從opckeytype派生的索引儲存型別。

attTypeleafType不同時,必須提供可選方法compress。方法compress負責將要索引的資料項從attType轉換為leafType

選擇

選擇將新值插入內部元組的方法。

SQL函式宣告必須如下所示

CREATE FUNCTION my_choose(internal, internal) RETURNS void ...

第一個引數是指向spgChooseIn C 結構體的指標,其中包含函式的輸入資料。第二個引數是指向spgChooseOut C 結構體的指標,函式必須用結果資料填充該結構體。

typedef struct spgChooseIn
{
    Datum       datum;          /* original datum to be indexed */
    Datum       leafDatum;      /* current datum to be stored at leaf */
    int         level;          /* current level (counting from zero) */

    /* Data from current inner tuple */
    bool        allTheSame;     /* tuple is marked all-the-same? */
    bool        hasPrefix;      /* tuple has a prefix? */
    Datum       prefixDatum;    /* if so, the prefix value */
    int         nNodes;         /* number of nodes in the inner tuple */
    Datum      *nodeLabels;     /* node label values (NULL if none) */
} spgChooseIn;

typedef enum spgChooseResultType
{
    spgMatchNode = 1,           /* descend into existing node */
    spgAddNode,                 /* add a node to the inner tuple */
    spgSplitTuple               /* split inner tuple (change its prefix) */
} spgChooseResultType;

typedef struct spgChooseOut
{
    spgChooseResultType resultType;     /* action code, see above */
    union
    {
        struct                  /* results for spgMatchNode */
        {
            int         nodeN;      /* descend to this node (index from 0) */
            int         levelAdd;   /* increment level by this much */
            Datum       restDatum;  /* new leaf datum */
        }           matchNode;
        struct                  /* results for spgAddNode */
        {
            Datum       nodeLabel;  /* new node's label */
            int         nodeN;      /* where to insert it (index from 0) */
        }           addNode;
        struct                  /* results for spgSplitTuple */
        {
            /* Info to form new upper-level inner tuple with one child tuple */
            bool        prefixHasPrefix;    /* tuple should have a prefix? */
            Datum       prefixPrefixDatum;  /* if so, its value */
            int         prefixNNodes;       /* number of nodes */
            Datum      *prefixNodeLabels;   /* their labels (or NULL for
                                             * no labels) */
            int         childNodeN;         /* which node gets child tuple */

            /* Info to form new lower-level inner tuple with all old nodes */
            bool        postfixHasPrefix;   /* tuple should have a prefix? */
            Datum       postfixPrefixDatum; /* if so, its value */
        }           splitTuple;
    }           result;
} spgChooseOut;

datum是要插入索引的spgConfigIn.attType型別的原始資料。leafDatumspgConfigOut.leafType型別的值,當提供了compress方法時,它最初是應用於datumcompress方法的結果,否則與datum相同。leafDatum可以在樹的較低級別更改,如果choosepicksplit方法更改了它。當插入搜尋到達葉頁時,leafDatum的當前值將儲存在新建立的葉元組中。level是當前內部元組的級別,根級別從零開始。allTheSame如果當前內部元組被標記為包含多個等效節點(請參閱第 65.3.4.3 節),則為 true。hasPrefix如果當前內部元組包含字首,則為 true;如果是,則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'd 陣列,如果不需要節點標籤則設定為 NULL。請注意,新上層元組的總大小不得超過其替換元組的總大小;這限制了新字首和新標籤的長度。將childNodeN設定為將向下連結到新的較低級別內部元組的節點的索引(從零開始)。設定postfixHasPrefix以指示新的較低級別內部元組是否應具有字首,如果是,則將postfixPrefixDatum設定為字首值。這兩個字首和向下連結節點的標籤(如果有)的組合必須與原始字首具有相同的含義,因為沒有機會更改移動到新的較低級別元組的節點標籤,也無法更改任何子索引條目。拆分節點後,將再次使用替換內部元組呼叫choose函式。如果spgSplitTuple操作沒有建立合適的節點,該呼叫可能會返回spgAddNode結果。最終choose必須返回spgMatchNode以允許插入下降到下一級別。

選擇拆分

決定如何在一組葉元組上建立新的內部元組。

SQL函式宣告必須如下所示

CREATE FUNCTION my_picksplit(internal, internal) RETURNS void ...

第一個引數是指向spgPickSplitIn C 結構體的指標,其中包含函式的輸入資料。第二個引數是指向spgPickSplitOut C 結構體的指標,函式必須用結果資料填充該結構體。

typedef struct spgPickSplitIn
{
    int         nTuples;        /* number of leaf tuples */
    Datum      *datums;         /* their datums (array of length nTuples) */
    int         level;          /* current level (counting from zero) */
} spgPickSplitIn;

typedef struct spgPickSplitOut
{
    bool        hasPrefix;      /* new inner tuple should have a prefix? */
    Datum       prefixDatum;    /* if so, its value */

    int         nNodes;         /* number of nodes for new inner tuple */
    Datum      *nodeLabels;     /* their labels (or NULL for no labels) */

    int        *mapTuplesToNodes;   /* node index for each leaf tuple */
    Datum      *leafTupleDatums;    /* datum to store in each new leaf tuple */
} spgPickSplitOut;

nTuples是提供的葉元組的數量。datums是其spgConfigOut.leafType型別的資料值陣列。level是所有葉元組共享的當前級別,這將成為新內部元組的級別。

設定hasPrefix以指示新內部元組是否應具有字首,如果是,則將prefixDatum設定為字首值。設定nNodes以指示新內部元組將包含的節點數量,並將nodeLabels設定為其標籤值的陣列,如果不需要節點標籤則設定為 NULL。將mapTuplesToNodes設定為一個數組,該陣列給出每個葉元組應分配到的節點的索引(從零開始)。將leafTupleDatums設定為要儲存在新葉元組中的值陣列(如果運算子類不修改從一個級別到下一個級別的資料,則這些值將與輸入datums相同)。請注意,picksplit函式負責分配nodeLabelsmapTuplesToNodesleafTupleDatums陣列的記憶體。

如果提供了多個葉元組,則期望picksplit函式將它們分類為多個節點;否則,無法將葉元組拆分到多個頁面中,這是此操作的最終目的。因此,如果picksplit函式最終將所有葉元組放置在同一個節點中,SP-GiST 核心程式碼將覆蓋該決定並生成一個內部元組,其中葉元組被隨機分配給幾個具有相同標籤的節點。此類元組被標記為allTheSame,以表示這種情況已經發生。chooseinner_consistent函式必須妥善處理此類內部元組。有關更多資訊,請參閱第 65.3.4.3 節

僅在config函式將longValuesOK設定為 true 且提供了大於頁面的輸入值的情況下,picksplit才能應用於單個葉元組。在這種情況下,操作的目的是剝離字首並生成一個新的、更短的葉資料值。該呼叫將重複,直到生成足夠短以適應頁面的葉資料值。有關更多資訊,請參閱第 65.3.4.1 節

inner_consistent

返回在樹搜尋期間要遵循的節點(分支)集。

SQL函式宣告必須如下所示

CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...

第一個引數是指向spgInnerConsistentIn C 結構體的指標,其中包含函式的輸入資料。第二個引數是指向spgInnerConsistentOut C 結構體的指標,函式必須用結果資料填充該結構體。

typedef struct spgInnerConsistentIn
{
    ScanKey     scankeys;       /* array of operators and comparison values */
    ScanKey     orderbys;       /* array of ordering operators and comparison
                                 * values */
    int         nkeys;          /* length of scankeys array */
    int         norderbys;      /* length of orderbys array */

    Datum       reconstructedValue;     /* value reconstructed at parent */
    void       *traversalValue; /* opclass-specific traverse value */
    MemoryContext traversalMemoryContext;   /* put new traverse values here */
    int         level;          /* current level (counting from zero) */
    bool        returnData;     /* original data must be returned? */

    /* Data from current inner tuple */
    bool        allTheSame;     /* tuple is marked all-the-same? */
    bool        hasPrefix;      /* tuple has a prefix? */
    Datum       prefixDatum;    /* if so, the prefix value */
    int         nNodes;         /* number of nodes in the inner tuple */
    Datum      *nodeLabels;     /* node label values (NULL if none) */
} spgInnerConsistentIn;

typedef struct spgInnerConsistentOut
{
    int         nNodes;         /* number of child nodes to be visited */
    int        *nodeNumbers;    /* their indexes in the node array */
    int        *levelAdds;      /* increment level by this much for each */
    Datum      *reconstructedValues;    /* associated reconstructed values */
    void      **traversalValues;        /* opclass-specific traverse values */
    double    **distances;              /* associated distances */
} spgInnerConsistentOut;

長度為nkeys的陣列scankeys描述了索引搜尋條件。這些條件與 AND 組合——只有滿足所有這些條件的索引條目才是有趣的。(請注意,nkeys = 0 意味著所有索引條目都滿足查詢。)通常,一致性函式只關心每個陣列條目的sk_strategysk_argument欄位,它們分別給出可索引的運算子和比較值。特別是,不需要檢查sk_flags以檢視比較值是否為 NULL,因為 SP-GiST 核心程式碼將過濾掉此類條件。長度為norderbys的陣列orderbys以相同的方式描述排序運算子(如果有)。reconstructedValue是為父元組重建的值;在根級別或如果inner_consistent函式在父級別未提供值時,它為(Datum) 0traversalValue是指向從父索引元組上inner_consistent的前一個呼叫傳遞下來的任何遍歷資料的指標,或者在根級別為 NULL。traversalMemoryContext是用於儲存輸出遍歷值的記憶體上下文(見下文)。level是當前內部元組的級別,根級別從零開始。returnData如果此查詢需要重建資料,則為true;只有在config函式聲明瞭canReturnData時才會如此。allTheSame如果當前內部元組被標記為“全部相同”,則為 true;在這種情況下,所有節點都具有相同的標籤(如果有),因此它們要麼全部匹配查詢,要麼都不匹配查詢(請參閱第 65.3.4.3 節)。hasPrefix如果當前內部元組包含字首,則為 true;如果是,則prefixDatum是其值。nNodes是內部元組中包含的子節點數量,nodeLabels是其標籤值的陣列,如果節點沒有標籤則為 NULL。

nNodes必須設定為需要被搜尋訪問的子節點數量,nodeNumbers必須設定為這些節點的索引陣列。如果運算子類跟蹤級別,則將levelAdds設定為下降到每個要訪問的節點時所需的級別增量陣列。(通常這些增量對於所有節點都相同,但這不一定,因此使用陣列。)如果需要值重建,則將reconstructedValues設定為為每個要訪問的子節點重建的值陣列;否則,將reconstructedValues設定為 NULL。重建的值假定為spgConfigOut.leafType型別。(然而,由於核心系統除了可能複製它們之外不會對它們做任何事情,因此它們具有與leafType相同的typlentypbyval屬性就足夠了。)如果執行有序搜尋,則根據orderbys陣列將distances設定為距離值陣列(距離最低的節點將首先處理)。否則將其設定為 NULL。如果希望將額外的帶外資訊(“遍歷值”)傳遞到樹搜尋的較低級別,則將traversalValues設定為適當的遍歷值陣列,每個要訪問的子節點一個;否則,將traversalValues設定為 NULL。請注意,inner_consistent函式負責在當前記憶體上下文中分配nodeNumberslevelAddsdistancesreconstructedValuestraversalValues陣列的記憶體。但是,traversalValues陣列指向的任何輸出遍歷值都應在traversalMemoryContext中分配。每個遍歷值必須是單個 palloc'd 塊。

leaf_consistent

如果葉元組滿足查詢,則返回 true。

SQL函式宣告必須如下所示

CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...

第一個引數是指向spgLeafConsistentIn C 結構體的指標,其中包含函式的輸入資料。第二個引數是指向spgLeafConsistentOut C 結構體的指標,函式必須用結果資料填充該結構體。

typedef struct spgLeafConsistentIn
{
    ScanKey     scankeys;       /* array of operators and comparison values */
    ScanKey     orderbys;       /* array of ordering operators and comparison
                                 * values */
    int         nkeys;          /* length of scankeys array */
    int         norderbys;      /* length of orderbys array */

    Datum       reconstructedValue;     /* value reconstructed at parent */
    void       *traversalValue; /* opclass-specific traverse value */
    int         level;          /* current level (counting from zero) */
    bool        returnData;     /* original data must be returned? */

    Datum       leafDatum;      /* datum in leaf tuple */
} spgLeafConsistentIn;

typedef struct spgLeafConsistentOut
{
    Datum       leafValue;        /* reconstructed original data, if any */
    bool        recheck;          /* set true if operator must be rechecked */
    bool        recheckDistances; /* set true if distances must be rechecked */
    double     *distances;        /* associated distances */
} spgLeafConsistentOut;

長度為nkeys的陣列scankeys描述了索引搜尋條件。這些條件與 AND 組合——只有滿足所有這些條件的索引條目才滿足查詢。(請注意,nkeys = 0 意味著所有索引條目都滿足查詢。)通常,一致性函式只關心每個陣列條目的sk_strategysk_argument欄位,它們分別給出可索引的運算子和比較值。特別是,不需要檢查sk_flags以檢視比較值是否為 NULL,因為 SP-GiST 核心程式碼將過濾掉此類條件。長度為norderbys的陣列orderbys以相同的方式描述排序運算子。reconstructedValue是為父元組重建的值;在根級別或如果inner_consistent函式在父級別未提供值時,它為(Datum) 0traversalValue是指向從父索引元組上inner_consistent的前一個呼叫傳遞下來的任何遍歷資料的指標,或者在根級別為 NULL。level是當前葉元組的級別,根級別從零開始。returnData如果此查詢需要重建資料,則為true;只有在config函式聲明瞭canReturnData時才會如此。leafDatum是儲存在當前葉元組中的spgConfigOut.leafType型別的鍵值。

如果葉元組匹配查詢,函式必須返回true,否則返回false。在true情況下,如果returnDatatrue,則leafValue必須設定為為此葉元組原始提供索引的值(型別為spgConfigIn.attType)。此外,如果匹配不確定,recheck可以設定為true,因此必須重新將運算子應用於實際的堆元組以驗證匹配。如果執行有序搜尋,則根據orderbys陣列將distances設定為距離值陣列。否則將其設定為 NULL。如果返回的至少一個距離不精確,則將recheckDistances設定為 true。在這種情況下,執行器將在從堆中獲取元組後計算精確距離,並在需要時重新排序元組。

可選的使用者定義方法是

Datum compress(Datum in)

將資料項轉換為適合在索引的葉元組中進行物理儲存的格式。它接受spgConfigIn.attType型別的值,並返回spgConfigOut.leafType型別的值。輸出值不得包含非內聯 TOAST 指標。

注意:compress方法僅適用於要儲存的值。一致性方法接收未更改的查詢scankeys,不透過compress進行轉換。

options

定義一組使用者可見的引數,用於控制運算子類的行為。

SQL函式宣告必須如下所示

CREATE OR REPLACE FUNCTION my_options(internal)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

該函式接收一個指向 local_relopts 結構的指標,該結構需要填充一組運算子類特定的選項。可以使用 PG_HAS_OPCLASS_OPTIONS()PG_GET_OPCLASS_OPTIONS() 宏從其他支援函式訪問這些選項。

由於SP-GiST中鍵的表示是靈活的,它可能取決於使用者指定的引數。

所有 SP-GiST 支援方法通常在短生命週期的記憶體上下文中呼叫;也就是說,在處理每個元組後,CurrentMemoryContext將被重置。因此,不必太擔心釋放所有 palloc 的記憶體。(config方法是一個例外:它應該儘量避免記憶體洩漏。但通常config方法除了將常量分配到傳遞的引數結構中之外,不需要做任何事情。)

如果索引列是可排序資料型別,則索引排序規則將使用標準的PG_GET_COLLATION()機制傳遞給所有支援方法。

65.3.4。實現 #

本節涵蓋對SP-GiST運算子類的實現者有用的實現細節和其他技巧。

65.3.4.1。SP-GiST 限制 #

單個葉元組和內部元組必須適合單個索引頁(預設為 8kB)。因此,在索引變長資料型別的值時,長值只能透過基數樹等方法支援,其中樹的每個級別都包含一個足夠短以適合頁面的字首,並且最終的葉級別包含一個同樣足夠短以適合頁面的字尾。運算子類應僅在準備好為此進行安排時將longValuesOK設定為 true。否則,SP-GiST核心程式碼將拒絕任何索引大於索引頁大小的值的請求。

同樣,運算子類有責任確保內部元組不會過大而無法容納在一個索引頁上;這限制了在一個內部元組中可以使用的子節點數量,以及字首值的最大大小。

另一個限制是,當一個內部元組的節點指向一組葉元組時,這些元組必須全部位於同一個索引頁中。(這是一個設計決定,旨在減少查詢和節省連結這些元組的空間。)如果葉元組集增長過大以至於無法容納在一個頁面中,則執行拆分並插入一箇中間內部元組。為了解決這個問題,新的內部元組必須將葉值集劃分為多個節點組。如果運算子類的picksplit函式未能做到這一點,SP-GiST核心程式碼將採取第 65.3.4.3 節中描述的特殊措施。

longValuesOK為 true 時,預期SP-GiST樹的連續級別將吸收越來越多的資訊到內部元組的字首和節點標籤中,使所需的葉資料變得越來越小,最終它將適合一個頁面。為了防止運算子類中的錯誤導致無限插入迴圈,如果葉資料在choose方法呼叫十個週期內沒有變小,SP-GiST核心程式碼將引發錯誤。

65.3.4.2。無節點標籤的 SP-GiST #

一些樹演算法為每個內部元組使用一組固定節點;例如,在四叉樹中,圍繞內部元組質心點的四個象限總是恰好有四個節點。在這種情況下,程式碼通常透過編號來處理節點,不需要顯式節點標籤。要抑制節點標籤(從而節省一些空間),picksplit函式可以為nodeLabels陣列返回 NULL,同樣,choose函式可以在spgSplitTuple操作期間為prefixNodeLabels陣列返回 NULL。這反過來將導致在後續呼叫chooseinner_consistent期間nodeLabels為 NULL。原則上,節點標籤可以用於某些內部元組,而對於同一索引中的其他內部元組則省略。

當處理具有無標籤節點的內部元組時,choose返回spgAddNode是錯誤的,因為在這種情況下節點集應該是固定的。

65.3.4.3。“全部相同”內部元組 #

SP-GiST核心程式碼可以在運算子類的picksplit函式未能將提供的葉值劃分為至少兩個節點類別時,覆蓋picksplit函式的結果。當這種情況發生時,將建立一個新的內部元組,其中包含多個節點,每個節點都具有picksplit用於它所使用的唯一節點的相同標籤(如果有),並且葉值在這些等效節點之間隨機分配。allTheSame標誌在內部元組上設定,以警告chooseinner_consistent函式,該元組不具有它們可能期望的節點集。

當處理allTheSame元組時,spgMatchNodechoose結果被解釋為新值可以分配給任何等效節點;核心程式碼將忽略提供的nodeN值並隨機下降到一個節點中(以便保持樹的平衡)。choose返回spgAddNode是錯誤的,因為那會使節點不再等效;如果要插入的值與現有節點不匹配,則必須使用spgSplitTuple操作。

當處理allTheSame元組時,inner_consistent函式應返回所有或不返回任何節點作為繼續索引搜尋的目標,因為它們都是等效的。這可能需要或可能不需要任何特殊情況程式碼,具體取決於inner_consistent函式通常對節點含義的假設程度。

65.3.5。示例 #

PostgreSQL 原始碼分發版中包含幾個SP-GiST索引運算子類的示例,如表 65.2中所述。請檢視src/backend/access/spgist/src/backend/utils/adt/以檢視程式碼。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的內容,請使用此表單報告文件問題。