支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2

64.3. SP-GiST 索引 #

64.3.1. 簡介 #

SP-GiST是空間分割 (space-partitioned) 的縮寫GiST. SP-GiST支援分割的搜尋樹,這有助於開發各種不同的非平衡資料結構,例如四元樹 (quad-trees)、k-d 樹和基數樹 (radix trees,又稱 tries)。這些結構的共同特點是,它們會重複地將搜尋空間分割成大小不等的區塊。與分割規則非常匹配的搜尋可以非常快速。

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

就像GiST, SP-GiST旨在允許資料類型領域的專家,而不是資料庫專家,開發具有適當存取方法的自訂資料類型。

此處的一些資訊來自 Purdue University 的 SP-GiST 索引專案 網站PostgreSQL 中的SP-GiST實作主要由 Teodor Sigaev 和 Oleg Bartunov 維護,他們的 網站上有更多資訊。

64.3.2. 內建運算子類別 #

核心 PostgreSQL 發行版本包含 表 64.2 所示的SP-GiST運算子類別。

表 64.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) 搜尋。

64.3.3. 擴充性 #

SP-GiST提供了一個具有高抽象層次的介面,要求存取方法開發人員僅實作特定於給定資料類型的方法。該SP-GiST核心負責高效的磁碟映射和搜尋樹結構。它還負責並行性和日誌記錄考量。

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

當一個SP-GiST索引使用 INCLUDE 欄位建立時,這些欄位的值也會儲存在葉節點組中。INCLUDE 欄位與SP-GiST運算子類別無關,因此這裡不再進一步討論。

內部節點組更複雜,因為它們是搜尋樹中的分支點。每個內部節點組包含一組或多個節點,這些節點表示相似葉子值的群組。節點包含一個下行鏈路,該鏈路通往另一個較低層級的內部節點組,或通往位於同一索引頁面上的短葉節點組清單。每個節點通常都有一個描述它的標籤;例如,在基數樹中,節點標籤可以是字串值的下一個字元。(或者,如果運算子類別處理所有內部節點組的固定節點集,則可以省略節點標籤;請參閱第 64.3.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 接受一個要索引的 datum 作為唯一參數,並返回適合在葉節點組中進行實體儲存的值。可選的第七個方法 options 接受一個指向 C 結構的 internal 指標,其中應放置特定於 opclass 的參數,並返回 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。僅當 attType 是可變長度,並且運算子類別能夠透過重複後綴來分割長值時,才應將 longValuesOK 設為 true(請參閱第 64.3.4.1 節)。

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

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

choose

選擇一種將新值插入內部節點組的方法。

核心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;

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

picksplit

決定如何在葉節點元組集合上建立一個新的內部元組。

核心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 函數負責 palloc 的 nodeLabelsmapTuplesToNodesleafTupleDatums 陣列。

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

只有在 config 函數設定 longValuesOK 為 true,且提供了大於一頁的輸入值時,picksplit 才能應用於單一葉節點元組。在這種情況下,操作的重點是剝離前綴,並產生一個新的、更短的葉節點資料值。這個呼叫會重複執行,直到產生一個短到足以容納在一頁上的葉節點資料。

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;在這種情況下,所有節點都具有相同的標籤(如果有的話),因此它們全部或全部不符合查詢(請參閱第 64.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 函數負責在目前的記憶體內容中 palloc 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 方法僅適用於要儲存的數值。 consistent 方法接收未經修改的查詢 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。 因此,不必過於擔心 pfree'ing 你 palloc 的所有內容。(config 方法是一個例外:它應該盡量避免記憶體洩漏。 但是,通常 config 方法只需要將常數分配給傳遞的參數結構即可。)

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

64.3.4. 實作 #

本節介紹實作細節和其他對SP-GiST運算子類別的實作者有用的技巧。

64.3.4.1. SP-GiST 限制 #

個別的葉節點元組和內部元組必須符合單一索引頁面(預設為 8kB)。 因此,當索引可變長度資料類型的值時,只有諸如基數樹之類的方法可以支援長值,在基數樹中,樹的每一層都包含一個足夠短以適合頁面的前綴,而最終的葉節點層也包含一個足夠短以適合頁面的後綴。 運算子類別僅應在其準備安排發生這種情況時,才將 longValuesOK 設定為 true。 否則,SP-GiST核心將拒絕索引太大而無法放入索引頁面的數值的任何請求。

同樣地,運算子類別有責任確保內部元組不會變得太大而無法放入索引頁面;這限制了一個內部元組中可以使用的子節點數量,以及前綴值的最大大小。

另一個限制是,當內部元組的節點指向一組葉節點元組時,這些元組必須全部位於同一個索引頁面中。(這是一個設計決策,目的是減少搜尋並節省將這些元組鏈接在一起的連結中的空間。)如果葉節點元組的集合變得太大而無法放入頁面,則會執行分割並插入一個中間內部元組。 為了修復此問題,新的內部元組必須將葉節點值的集合劃分為多個節點群組。 如果運算子類別的 picksplit 函數未能做到這一點,SP-GiST核心將採用 第 64.3.4.3 節 中描述的特殊措施。

longValuesOK 為 true 時,預期SP-GiST樹的後續層將越來越多的資訊吸收到內部元組的前綴和節點標籤中,使所需的葉節點資料越來越小,最終可以放入頁面中。 為了防止運算子類別中的錯誤導致無限插入迴圈,如果葉節點資料在十個 choose 方法呼叫週期內沒有變小,則SP-GiST核心將引發錯誤。

64.3.4.2. 沒有節點標籤的 SP-GiST #

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

當處理具有未標記節點的內部元組時,choose 傳回 spgAddNode 是一個錯誤,因為在這種情況下,節點的集合應該是固定的。

64.3.4.3. 全部相同內部元組 #

核心SP-GiSTpicksplit 無法將提供的葉節點值劃分為至少兩個節點類別時,核心可以覆寫運算子類別的 picksplit 函數的結果。 發生這種情況時,會建立一個新的內部元組,其中包含多個節點,每個節點都具有與 picksplit 給予它使用的節點相同的標籤(如果有的話),並且葉節點值在這些等效節點之間隨機劃分。 在內部元組上設定 allTheSame 標記,以警告 chooseinner_consistent 函數,該元組沒有它們可能期望的節點集合。

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

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

64.3.5. 範例 #

PostgreSQL 原始碼發行版包含幾個用於SP-GiST的索引運算子類別範例,如表 64.2 中所述。 查看 src/backend/access/spgist/src/backend/utils/adt/ 以查看程式碼。

提交更正

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