支援的版本: 目前 (17) / 16 / 15 / 14 / 13
開發版本: 開發版
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

64.2. GiST 索引 #

64.2.1. 簡介 #

GiST代表廣義搜尋樹 (Generalized Search Tree)。它是一種平衡的樹狀結構存取方法,可作為實作任意索引架構的基本範本。 B 樹、R 樹和許多其他索引架構都可以實作在GiST.

的優點之一GiST在於它允許由資料類型領域的專家,而不是資料庫專家,開發具有適當存取方法的自訂資料類型。

此處的一些資訊來自加州大學柏克萊分校的 GiST 索引專案 網站和 Marcel Kornacker 的論文 下一代資料庫系統的存取方法PostgreSQL 中的GiST實作主要由 Teodor Sigaev 和 Oleg Bartunov 維護,更多資訊請參閱他們的 網站

64.2.2. 內建運算子類別 #

核心 PostgreSQL 發行版本包含 表 64.1 中所示的GiST運算子類別。(附錄 F 中描述的一些選用模組提供了額外的GiST運算子類別。)

表 64.1. 內建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)
circle_ops << (circle, circle) <-> (circle, point)
&< (circle, circle)
&> (circle, circle)
>> (circle, circle)
<@ (circle, circle)
@> (circle, circle)
~= (circle, circle)
&& (circle, circle)
|>> (circle, circle)
<<| (circle, circle)
&<| (circle, circle)
|&> (circle, circle)
inet_ops << (inet, inet)  
<<= (inet, inet)
>> (inet, inet)
>>= (inet, inet)
= (inet, inet)
<> (inet, inet)
< (inet, inet)
<= (inet, inet)
> (inet, inet)
>= (inet, inet)
&& (inet, inet)
multirange_ops = (anymultirange, anymultirange)  
&& (anymultirange, anymultirange)
&& (anymultirange, anyrange)
@> (anymultirange, anyelement)
@> (anymultirange, anymultirange)
@> (anymultirange, anyrange)
<@ (anymultirange, anymultirange)
<@ (anymultirange, anyrange)
<< (anymultirange, anymultirange)
<< (anymultirange, anyrange)
>> (anymultirange, anymultirange)
>> (anymultirange, anyrange)
&< (anymultirange, anymultirange)
&< (anymultirange, anyrange)
&> (anymultirange, anymultirange)
&> (anymultirange, anyrange)
-|- (anymultirange, anymultirange)
-|- (anymultirange, anyrange)
point_ops |>> (point, point) <-> (point, point)
<< (point, point)
>> (point, point)
<<| (point, point)
~= (point, point)
<@ (point, box)
<@ (point, polygon)
<@ (point, circle)
poly_ops << (polygon, polygon) <-> (polygon, point)
&< (polygon, polygon)
&> (polygon, polygon)
>> (polygon, polygon)
<@ (polygon, polygon)
@> (polygon, polygon)
~= (polygon, polygon)
&& (polygon, polygon)
<<| (多邊形, 多邊形)
&<| (多邊形, 多邊形)
|&> (多邊形, 多邊形)
|>> (多邊形, 多邊形)
range_ops = (任意範圍, 任意範圍)  
&& (任意範圍, 任意範圍)
&& (任意範圍, 任意多重範圍)
@> (任意範圍, 任意元素)
@> (任意範圍, 任意範圍)
@> (任意範圍, 任意多重範圍)
<@ (任意範圍, 任意範圍)
<@ (任意範圍, 任意多重範圍)
<< (任意範圍, 任意範圍)
<< (任意範圍, 任意多重範圍)
>> (任意範圍, 任意範圍)
>> (任意範圍, 任意多重範圍)
&< (任意範圍, 任意範圍)
&< (任意範圍, 任意多重範圍)
&> (任意範圍, 任意範圍)
&> (任意範圍, 任意多重範圍)
-|- (任意範圍, 任意範圍)
-|- (任意範圍, 任意多重範圍)
tsquery_ops <@ (tsquery, tsquery)  
@> (tsquery, tsquery)
tsvector_ops @@ (tsvector, tsquery)  

由於歷史原因,inet_ops 運算符類別不是 inetcidr 類型的預設類別。要使用它,請在 CREATE INDEX 中提及類別名稱,例如

CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);

64.2.3. 可擴展性 #

傳統上,實現新的索引存取方法意味著大量困難的工作。有必要了解資料庫的內部運作,例如鎖定管理器和預寫式記錄。但是GiST介面具有高度的抽象性,僅要求存取方法實作者實現正在存取的資料類型的語意。該GiST層本身負責並發、記錄和搜尋樹狀結構。

這種可擴展性不應與其他標準搜尋樹在其可以處理的資料方面的可擴展性混淆。例如,PostgreSQL 支援可擴展的 B 樹和雜湊索引。這意味著您可以使用 PostgreSQL 來建構任何您想要的資料類型的 B 樹或雜湊。但是 B 樹僅支援範圍謂詞(<=>),而雜湊索引僅支援相等查詢。

因此,如果您使用 PostgreSQL B 樹為影像集合建立索引,則您只能發出諸如 imagex 是否等於 imageyimagex 是否小於 imageyimagex 是否大於 imagey 之類的查詢。根據您如何在此上下文中定義 等於小於大於,這可能很有用。但是,透過使用GiST索引,您可以建立詢問特定領域問題的方法,例如 找到所有馬的圖片找到所有曝光過度的圖片

只需GiST存取方法啟動並執行,就是實現多個使用者定義的方法,這些方法定義了樹中鍵的行為。當然,這些方法必須非常精巧才能支援精巧的查詢,但對於所有標準查詢(B 樹、R 樹等),它們都相對簡單。簡而言之,GiST結合了可擴展性以及通用性、程式碼重用和清晰的介面。

對於GiST,索引運算符類別必須提供五種方法,以及六種可選方法。索引的正確性透過正確實作 sameconsistentunion 方法來確保,而索引的效率(大小和速度)將取決於 penaltypicksplit 方法。兩種可選方法是 compressdecompress,它們允許索引具有與其索引的資料類型不同的內部樹資料。葉子應為索引資料類型,而其他樹節點可以是任何 C 結構(但您仍然必須遵循 PostgreSQL 資料類型規則,請參閱關於變長資料 varlena)。如果樹的內部資料類型存在於 SQL 層級,則可以使用 CREATE OPERATOR CLASS 命令的 STORAGE 選項。可選的第八種方法是 distance,如果運算符類別希望支援排序掃描(最近鄰搜尋),則需要它。如果運算符類別希望支援僅索引掃描(除非省略 compress 方法),則需要可選的第九種方法 fetch。如果運算符類別具有使用者指定的參數,則需要可選的第十種方法 options。可選的第十一種方法 sortsupport 用於加速建構GiST索引。

consistent

給定索引條目 p 和查詢值 q,此函數確定索引條目是否與查詢一致;也就是說,對於索引條目表示的任何行,謂詞 indexed_column indexable_operator q是否可能為真?對於葉子索引條目,這相當於測試可索引條件,而對於內部樹節點,這確定是否需要掃描樹節點表示的索引子樹。當結果為 true 時,也必須傳回 recheck 旗標。這表示謂詞是肯定為真還是僅可能為真。如果 recheck = false,則索引已準確地測試了謂詞條件,而如果 recheck = true,則該行僅是候選符合項。在這種情況下,系統將自動針對實際行值評估 indexable_operator,以查看它是否真的是符合項。此慣例允許GiST支援無損和有損索引結構。

SQL函數宣告必須如下所示

CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal)
RETURNS bool
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_consistent);

Datum
my_consistent(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    data_type  *query = PG_GETARG_DATA_TYPE_P(1);
    StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
    /* Oid subtype = PG_GETARG_OID(3); */
    bool       *recheck = (bool *) PG_GETARG_POINTER(4);
    data_type  *key = DatumGetDataType(entry->key);
    bool        retval;

    /*
     * determine return value as a function of strategy, key and query.
     *
     * Use GIST_LEAF(entry) to know where you're called in the index tree,
     * which comes handy when supporting the = operator for example (you could
     * check for non empty union() in non-leaf nodes and equality in leaf
     * nodes).
     */

    *recheck = true;        /* or false if check is exact */

    PG_RETURN_BOOL(retval);
}

在這裡,key 是索引中的元素,而 query 是在索引中查找到的值。StrategyNumber 參數表示正在應用您的運算符類別的哪個運算符 - 它與 CREATE OPERATOR CLASS 命令中的運算符編號之一相符。

根據您在類別中包含的運算符,query 的資料類型可能會因運算符而異,因為它將是運算符右側的任何類型,這可能與出現在左側的索引資料類型不同。(上面的程式碼框架假設只有一種類型是可能的;如果不是,則獲取 query 引數值必須取決於運算符。)建議 consistent 函數的 SQL 宣告使用 opclass 的索引資料類型作為 query 引數,即使實際類型可能是其他類型,具體取決於運算符。

union

這個方法會整合樹狀結構中的資訊。給定一組條目,這個函數會產生一個新的索引條目,代表所有給定的條目。

SQL函數宣告必須如下所示

CREATE OR REPLACE FUNCTION my_union(internal, internal)
RETURNS storage_type
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_union);

Datum
my_union(PG_FUNCTION_ARGS)
{
    GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
    GISTENTRY  *ent = entryvec->vector;
    data_type  *out,
               *tmp,
               *old;
    int         numranges,
                i = 0;

    numranges = entryvec->n;
    tmp = DatumGetDataType(ent[0].key);
    out = tmp;

    if (numranges == 1)
    {
        out = data_type_deep_copy(tmp);

        PG_RETURN_DATA_TYPE_P(out);
    }

    for (i = 1; i < numranges; i++)
    {
        old = out;
        tmp = DatumGetDataType(ent[i].key);
        out = my_union_implementation(out, tmp);
    }

    PG_RETURN_DATA_TYPE_P(out);
}

正如你所看到的,在這個骨架中,我們處理的是一種資料類型,其中 union(X, Y, Z) = union(union(X, Y), Z)。透過在這個函式中實作適當的 union 演算法,可以很容易地支援這種情況不成立的資料類型GiST支援方法。

union 函數的結果必須是索引儲存類型的值,無論它是什麼(它可能與索引欄位的類型相同或不同)。union 函數應該傳回一個指向新 palloc()配置記憶體的指標。你不能直接傳回輸入值,即使沒有類型更改。

如上所示,union 函數的第一個 internal 參數實際上是一個 GistEntryVector 指標。第二個參數是指向整數變數的指標,可以忽略。(過去需要 union 函數將其結果值的大小儲存到該變數中,但現在已不再需要。)

compress

將資料項目轉換為適合在索引頁面中進行物理儲存的格式。 如果省略 compress 方法,則資料項目將未經修改地儲存在索引中。

SQL函數宣告必須如下所示

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

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_compress);

Datum
my_compress(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    GISTENTRY  *retval;

    if (entry->leafkey)
    {
        /* replace entry->key with a compressed version */
        compressed_data_type *compressed_data = palloc(sizeof(compressed_data_type));

        /* fill *compressed_data from entry->key ... */

        retval = palloc(sizeof(GISTENTRY));
        gistentryinit(*retval, PointerGetDatum(compressed_data),
                      entry->rel, entry->page, entry->offset, FALSE);
    }
    else
    {
        /* typically we needn't do anything with non-leaf entries */
        retval = entry;
    }

    PG_RETURN_POINTER(retval);
}

您必須調整 compressed_data_type 以適應您要轉換成的特定類型,以便壓縮您的葉節點,這是當然的。

decompress

將資料項目的儲存表示形式轉換為可以被運算符類別中其他 GiST 方法操作的格式。 如果省略 decompress 方法,則假定其他 GiST 方法可以直接處理儲存的資料格式。(decompress 不一定是 compress 方法的逆運算;特別是,如果 compress 是有損的,則 decompress 就不可能完全重建原始資料。decompress 也不一定等同於 fetch,因為其他 GiST 方法可能不需要完全重建資料。)

SQL函數宣告必須如下所示

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

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_decompress);

Datum
my_decompress(PG_FUNCTION_ARGS)
{
    PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}

上面的骨架適用於不需要解壓縮的情況。(但是,當然,完全省略該方法更容易,並且在這種情況下建議使用。)

penalty

傳回一個值,指示將新條目插入樹的特定分支的 成本。 項目將沿著樹中最小 penalty 的路徑插入。 penalty 傳回的值應為非負數。 如果傳回負值,則將其視為零。

SQL函數宣告必須如下所示

CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;  -- in some cases penalty functions need not be strict

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_penalty);

Datum
my_penalty(PG_FUNCTION_ARGS)
{
    GISTENTRY  *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
    GISTENTRY  *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
    float      *penalty = (float *) PG_GETARG_POINTER(2);
    data_type  *orig = DatumGetDataType(origentry->key);
    data_type  *new = DatumGetDataType(newentry->key);

    *penalty = my_penalty_implementation(orig, new);
    PG_RETURN_POINTER(penalty);
}

由於歷史原因,penalty 函數不僅僅傳回 float 結果;而是必須將該值儲存在第三個參數指示的位置。 就其本身而言,傳回值將被忽略,儘管通常會傳回該參數的地址。

penalty 函數對於索引的良好性能至關重要。 在插入時,它將用於確定在選擇將新條目添加到樹中的哪個分支時要遵循哪個分支。 在查詢時,索引越平衡,查找就越快。

picksplit

當需要分割索引頁面時,此函數決定頁面上的哪些條目保留在舊頁面上,以及哪些條目要移動到新頁面上。

SQL函數宣告必須如下所示

CREATE OR REPLACE FUNCTION my_picksplit(internal, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_picksplit);

Datum
my_picksplit(PG_FUNCTION_ARGS)
{
    GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
    GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
    OffsetNumber maxoff = entryvec->n - 1;
    GISTENTRY  *ent = entryvec->vector;
    int         i,
                nbytes;
    OffsetNumber *left,
               *right;
    data_type  *tmp_union;
    data_type  *unionL;
    data_type  *unionR;
    GISTENTRY **raw_entryvec;

    maxoff = entryvec->n - 1;
    nbytes = (maxoff + 1) * sizeof(OffsetNumber);

    v->spl_left = (OffsetNumber *) palloc(nbytes);
    left = v->spl_left;
    v->spl_nleft = 0;

    v->spl_right = (OffsetNumber *) palloc(nbytes);
    right = v->spl_right;
    v->spl_nright = 0;

    unionL = NULL;
    unionR = NULL;

    /* Initialize the raw entry vector. */
    raw_entryvec = (GISTENTRY **) malloc(entryvec->n * sizeof(void *));
    for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
        raw_entryvec[i] = &(entryvec->vector[i]);

    for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
    {
        int         real_index = raw_entryvec[i] - entryvec->vector;

        tmp_union = DatumGetDataType(entryvec->vector[real_index].key);
        Assert(tmp_union != NULL);

        /*
         * Choose where to put the index entries and update unionL and unionR
         * accordingly. Append the entries to either v->spl_left or
         * v->spl_right, and care about the counters.
         */

        if (my_choice_is_left(unionL, curl, unionR, curr))
        {
            if (unionL == NULL)
                unionL = tmp_union;
            else
                unionL = my_union_implementation(unionL, tmp_union);

            *left = real_index;
            ++left;
            ++(v->spl_nleft);
        }
        else
        {
            /*
             * Same on the right
             */
        }
    }

    v->spl_ldatum = DataTypeGetDatum(unionL);
    v->spl_rdatum = DataTypeGetDatum(unionR);
    PG_RETURN_POINTER(v);
}

請注意,picksplit 函數的結果是透過修改傳入的 v 結構來傳遞的。 就其本身而言,傳回值將被忽略,儘管通常會傳回 v 的地址。

penalty 類似,picksplit 函數對於索引的良好性能至關重要。 設計合適的 penaltypicksplit 實現是實現良好性能GiST索引的挑戰所在。

same

如果兩個索引條目相同,則傳回 true,否則傳回 false。(索引條目是索引儲存類型的值,不一定是原始索引欄位的類型。)

SQL函數宣告必須如下所示

CREATE OR REPLACE FUNCTION my_same(storage_type, storage_type, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_same);

Datum
my_same(PG_FUNCTION_ARGS)
{
    prefix_range *v1 = PG_GETARG_PREFIX_RANGE_P(0);
    prefix_range *v2 = PG_GETARG_PREFIX_RANGE_P(1);
    bool       *result = (bool *) PG_GETARG_POINTER(2);

    *result = my_eq(v1, v2);
    PG_RETURN_POINTER(result);
}

由於歷史原因,same 函數不僅僅傳回布林值結果;而是必須將該標誌儲存在第三個參數指示的位置。 就其本身而言,傳回值將被忽略,儘管通常會傳回該參數的地址。

distance

給定一個索引條目 p 和一個查詢值 q,此函數確定索引條目與查詢值的 距離。 如果運算符類別包含任何排序運算符,則必須提供此函數。 使用排序運算符的查詢將透過首先傳回具有最小 距離 值的索引條目來實現,因此結果必須與運算符的語意一致。 對於葉索引條目,結果僅表示到索引條目的距離;對於內部樹節點,結果必須是任何子條目可能具有的最小距離。

SQL函數宣告必須如下所示

CREATE OR REPLACE FUNCTION my_distance(internal, data_type, smallint, oid, internal)
RETURNS float8
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

C 模組中對應的程式碼可以遵循以下框架

PG_FUNCTION_INFO_V1(my_distance);

Datum
my_distance(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    data_type  *query = PG_GETARG_DATA_TYPE_P(1);
    StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
    /* Oid subtype = PG_GETARG_OID(3); */
    /* bool *recheck = (bool *) PG_GETARG_POINTER(4); */
    data_type  *key = DatumGetDataType(entry->key);
    double      retval;

    /*
     * determine return value as a function of strategy, key and query.
     */

    PG_RETURN_FLOAT8(retval);
}

distance 函數的參數與 consistent 函數的參數相同。

在確定距離時允許進行一些近似,只要結果永遠不大於條目的實際距離即可。 因此,例如,在幾何應用中,到邊界框的距離通常就足夠了。 對於內部樹節點,傳回的距離不得大於到任何子節點的距離。 如果傳回的距離不準確,則該函數必須將 *recheck 設定為 true。(對於內部樹節點,這不是必需的;對於它們,始終假定計算是不精確的。)在這種情況下,執行器將在從堆中提取元組後計算準確的距離,並在必要時重新排序元組。

如果距離函數為任何葉節點傳回 *recheck = true,則原始排序運算符的傳回類型必須為 float8float4,並且距離函數的結果值必須與原始排序運算符的結果值進行比較,因為執行器將使用距離函數結果和重新計算的排序運算符結果進行排序。 否則,距離函數的結果值可以是任何有限的 float8 值,只要結果值的相對順序與排序運算符傳回的順序相符即可。(無窮大和負無窮大在內部用於處理空值等情況,因此不建議 distance 函數傳回這些值。)

fetch

將資料項目的壓縮索引表示形式轉換為原始資料類型,用於僅索引掃描。傳回的資料必須是原始索引值的精確、無損副本。

SQL函數宣告必須如下所示

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

參數是指向 GISTENTRY 結構的指標。在進入時,其 key 欄位包含以壓縮形式表示的非 NULL 葉節點資料。傳回值是另一個 GISTENTRY 結構,其 key 欄位包含相同資料的原始、未壓縮形式。如果運算子類別的壓縮函數對葉節點條目沒有執行任何操作,則 fetch 方法可以原樣傳回引數。或者,如果運算子類別沒有壓縮函數,則也可以省略 fetch 方法,因為它必然是一個空操作。

C 模組中匹配的程式碼可以遵循以下骨架:

PG_FUNCTION_INFO_V1(my_fetch);

Datum
my_fetch(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    input_data_type *in = DatumGetPointer(entry->key);
    fetched_data_type *fetched_data;
    GISTENTRY  *retval;

    retval = palloc(sizeof(GISTENTRY));
    fetched_data = palloc(sizeof(fetched_data_type));

    /*
     * Convert 'fetched_data' into the a Datum of the original datatype.
     */

    /* fill *retval from fetched_data. */
    gistentryinit(*retval, PointerGetDatum(converted_datum),
                  entry->rel, entry->page, entry->offset, FALSE);

    PG_RETURN_POINTER(retval);
}

如果壓縮方法對葉節點條目是有損的,則運算子類別無法支援僅索引掃描,並且不得定義 fetch 函數。

選項

允許定義使用者可見的參數,以控制運算子類別的行為。

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() 巨集從其他支援函數存取這些選項。

下面給出了 my_options() 的示例實作以及其他支援函數使用的參數。

typedef enum MyEnumType
{
    MY_ENUM_ON,
    MY_ENUM_OFF,
    MY_ENUM_AUTO
} MyEnumType;

typedef struct
{
    int32   vl_len_;    /* varlena header (do not touch directly!) */
    int     int_param;  /* integer parameter */
    double  real_param; /* real parameter */
    MyEnumType enum_param; /* enum parameter */
    int     str_param;  /* string parameter */
} MyOptionsStruct;

/* String representation of enum values */
static relopt_enum_elt_def myEnumValues[] =
{
    {"on", MY_ENUM_ON},
    {"off", MY_ENUM_OFF},
    {"auto", MY_ENUM_AUTO},
    {(const char *) NULL}   /* list terminator */
};

static char *str_param_default = "default";

/*
 * Sample validator: checks that string is not longer than 8 bytes.
 */
static void
validate_my_string_relopt(const char *value)
{
    if (strlen(value) > 8)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("str_param must be at most 8 bytes")));
}

/*
 * Sample filler: switches characters to lower case.
 */
static Size
fill_my_string_relopt(const char *value, void *ptr)
{
    char   *tmp = str_tolower(value, strlen(value), DEFAULT_COLLATION_OID);
    int     len = strlen(tmp);

    if (ptr)
        strcpy((char *) ptr, tmp);

    pfree(tmp);
    return len + 1;
}

PG_FUNCTION_INFO_V1(my_options);

Datum
my_options(PG_FUNCTION_ARGS)
{
    local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0);

    init_local_reloptions(relopts, sizeof(MyOptionsStruct));
    add_local_int_reloption(relopts, "int_param", "integer parameter",
                            100, 0, 1000000,
                            offsetof(MyOptionsStruct, int_param));
    add_local_real_reloption(relopts, "real_param", "real parameter",
                             1.0, 0.0, 1000000.0,
                             offsetof(MyOptionsStruct, real_param));
    add_local_enum_reloption(relopts, "enum_param", "enum parameter",
                             myEnumValues, MY_ENUM_ON,
                             "Valid values are: \"on\", \"off\" and \"auto\".",
                             offsetof(MyOptionsStruct, enum_param));
    add_local_string_reloption(relopts, "str_param", "string parameter",
                               str_param_default,
                               &validate_my_string_relopt,
                               &fill_my_string_relopt,
                               offsetof(MyOptionsStruct, str_param));

    PG_RETURN_VOID();
}

PG_FUNCTION_INFO_V1(my_compress);

Datum
my_compress(PG_FUNCTION_ARGS)
{
    int     int_param = 100;
    double  real_param = 1.0;
    MyEnumType enum_param = MY_ENUM_ON;
    char   *str_param = str_param_default;

    /*
     * Normally, when opclass contains 'options' method, then options are always
     * passed to support functions.  However, if you add 'options' method to
     * existing opclass, previously defined indexes have no options, so the
     * check is required.
     */
    if (PG_HAS_OPCLASS_OPTIONS())
    {
        MyOptionsStruct *options = (MyOptionsStruct *) PG_GET_OPCLASS_OPTIONS();

        int_param = options->int_param;
        real_param = options->real_param;
        enum_param = options->enum_param;
        str_param = GET_STRING_RELOPTION(options, str_param);
    }

    /* the rest implementation of support function */
}

由於索引中 key 的表示形式GiST具有彈性,因此可能取決於使用者指定的參數。例如,可以指定 key 簽章的長度。請參閱 gtsvector_options() 取得範例。

排序支援

傳回一個比較器函數,以一種保留區域性的方式對資料進行排序。它被 CREATE INDEXREINDEX 命令使用。所建立索引的品質取決於比較器函數確定的排序如何良好地保留輸入的區域性。

sortsupport 方法是可選的。如果未提供,則 CREATE INDEX 會使用 penaltypicksplit 函數將每個元組插入到樹中來建立索引,這會慢得多。

SQL函數宣告必須如下所示

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

參數是指向 SortSupport 結構的指標。至少,該函數必須填寫其 comparator 欄位。比較器採用三個參數:兩個要比較的 Datums,以及指向 SortSupport 結構的指標。Datums 是以儲存在索引中的格式表示的兩個索引值;也就是說,以 compress 方法傳回的格式表示。完整的 API 定義在 src/include/utils/sortsupport.h 中。

C 模組中匹配的程式碼可以遵循以下骨架:

PG_FUNCTION_INFO_V1(my_sortsupport);

static int
my_fastcmp(Datum x, Datum y, SortSupport ssup)
{
  /* establish order between x and y by computing some sorting value z */

  int z1 = ComputeSpatialCode(x);
  int z2 = ComputeSpatialCode(y);

  return z1 == z2 ? 0 : z1 > z2 ? 1 : -1;
}

Datum
my_sortsupport(PG_FUNCTION_ARGS)
{
  SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);

  ssup->comparator = my_fastcmp;
  PG_RETURN_VOID();
}

所有 GiST 支援方法通常都在短暫的記憶體環境中呼叫;也就是說,CurrentMemoryContext 會在每個元組處理後重設。因此,不必太擔心 pfree'ing 所有您 palloc 的東西。但是,在某些情況下,支援方法跨重複呼叫快取資料很有用。為此,請在 fcinfo->flinfo->fn_mcxt 中分配較長生命週期的資料,並在 fcinfo->flinfo->fn_extra 中保留指向它的指標。此類資料將在索引操作的生命週期內(例如,單個 GiST 索引掃描、索引建立或索引元組插入)存留。替換 fn_extra 值時請小心 pfree 之前的值,否則洩漏會在操作期間累積。

64.2.4. 實作 #

64.2.4.1. GiST 索引建立方法 #

建立 GiST 索引的最簡單方法就是一個接一個地插入所有條目。對於大型索引來說,這往往很慢,因為如果索引元組分散在整個索引中,並且索引大到無法放入快取,則需要大量的隨機 I/O。PostgreSQL 支援兩種替代方法來初始建立 GiST 索引:排序緩衝模式。

只有當索引使用的每個運算子類別都提供 sortsupport 函數時,排序方法才可用,如 第 64.2.3 節中所述。如果它們提供了,那麼這種方法通常是最好的,所以預設使用它。

緩衝方法的工作原理是不要立即將元組直接插入到索引中。它可以顯著減少非排序資料集所需的隨機 I/O 量。對於排序良好的資料集,好處較小或不存在,因為一次只有少數頁面接收到新的元組,即使索引整體不適合快取,這些頁面也能放入快取。

緩衝方法需要比簡單方法更頻繁地呼叫 penalty 函數,這會消耗一些額外的 CPU 資源。此外,緩衝區需要臨時磁碟空間,最高可達結果索引的大小。緩衝也會影響結果索引的品質,無論是正面還是負面。這種影響取決於各種因素,例如輸入資料的分佈和運算子類別的實作。

如果無法排序,則預設情況下,當索引大小達到 effective_cache_size 時,GiST 索引建立會切換到緩衝方法。可以使用 CREATE INDEX 命令的 buffering 參數手動強制或阻止緩衝。預設行為適用於大多數情況,但如果輸入資料已排序,則關閉緩衝可能會稍微加快建立速度。

64.2.5. 範例 #

PostgreSQL 原始碼發行版包含使用以下方法實作的索引方法的幾個範例:GiST。核心系統目前提供文字搜尋支援(tsvectortsquery 的索引),以及一些內建幾何資料類型的 R-Tree 等效功能(請參閱 src/backend/access/gist/gistproc.c)。以下 contrib 模組也包含:GiST運算子類別

btree_gist

多種資料類型的 B 樹等效功能

cube

多維立方體的索引

hstore

用於儲存(鍵、值)對的模組

intarray

int4 值的一維陣列的 RD-Tree

ltree

樹狀結構的索引

pg_trgm

使用三字母組匹配的文字相似性

seg

浮點範圍的索引

提交更正

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