支援版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援版本: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)
<<| (polygon, polygon)
&<| (polygon, polygon)
|&> (polygon, polygon)
|>> (polygon, polygon)
range_ops = (anyrange, anyrange)  
&& (anyrange, anyrange)
&& (anyrange, anymultirange)
@> (anyrange, anyelement)
@> (anyrange, anyrange)
@> (anyrange, anymultirange)
<@ (anyrange, anyrange)
<@ (anyrange, anymultirange)
<< (anyrange, anyrange)
<< (anyrange, anymultirange)
>> (anyrange, anyrange)
>> (anyrange, anymultirange)
&< (anyrange, anyrange)
&< (anyrange, anymultirange)
&> (anyrange, anyrange)
&> (anyrange, anymultirange)
-|- (anyrange, anyrange)
-|- (anyrange, anymultirange)
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. 可擴充性 #

傳統上,實作新的索引存取方法意味著許多困難的工作。需要了解資料庫的內部運作,例如鎖定管理器和預寫式日誌。TheGiST介面具有高度的抽象性,要求存取方法實作者僅實作所存取資料類型的語意。TheGiST層本身負責並行性、記錄和搜尋樹狀結構。

這種可擴充性不應與其他標準搜尋樹在可以處理的資料方面的可擴充性混淆。例如,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,如果運算符類別希望支援排序掃描(最近鄰搜尋),則需要它。如果運算符類別希望支援僅索引掃描,則需要可選的第九個方法 fetch,除非省略 compress 方法。如果運算符類別具有使用者指定的參數,則需要可選的第十個方法 options。可選的第十一個方法 sortsupport 用於加速建立GiST索引。

consistent

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

TheSQL函數的宣告必須如下所示

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 (聯集)

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

TheSQL函數的宣告必須如下所示

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)。透過在此方法中實現適當的聯集演算法,可以很容易地支援不是這種情況的資料類型。GiSTsupport method (支援方法)。

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

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

compress (壓縮)

將資料項目轉換為適合在索引頁面中進行實體儲存的格式。如果省略 compress 方法,則資料項目將在索引中儲存而不進行修改。

TheSQL函數的宣告必須如下所示

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 方法可能不需要完全重建資料。)

TheSQL函數的宣告必須如下所示

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 傳回的值應為非負數。如果傳回負值,則將其視為零。

TheSQL函數的宣告必須如下所示

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 (選擇分割)

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

TheSQL函數的宣告必須如下所示

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 實作是實現高效能索引的挑戰所在。GiSTindexes (索引) lies (所在)。

same (相同)

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

TheSQL函數的宣告必須如下所示

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,此函數確定索引條目與查詢值的 距離。如果運算子類別包含任何排序運算子,則必須提供此函數。使用排序運算子的查詢將透過首先傳回具有最小 距離值的索引條目來實現,因此結果必須與運算子的語義一致。對於葉索引條目,結果僅代表到索引條目的距離;對於內部樹狀節點,結果必須是任何子條目可能具有的最小距離。

TheSQL函數的宣告必須如下所示

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 值,只要結果值的相對順序與排序運算子回傳的順序一致即可。(無限大和負無限大在內部用於處理 null 等情況,因此不建議 distance 函數回傳這些值。)

fetch

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

TheSQL函數的宣告必須如下所示

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

此引數是指向 GISTENTRY 結構的指標。在輸入時,其 key 欄位包含壓縮形式的非 NULL 葉節點資料。回傳值是另一個 GISTENTRY 結構,其 key 欄位包含其原始、未壓縮形式的相同資料。如果 opclass 的 compress 函數對葉節點條目沒有執行任何操作,則 fetch 方法可以按原樣回傳引數。或者,如果 opclass 沒有 compress 函數,也可以省略 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);
}

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

options

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

TheSQL函數的宣告必須如下所示

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 */
}

由於金鑰的表示形式在GiST具有彈性,它可能取決於使用者指定的參數。例如,可以指定金鑰簽章的長度。有關範例,請參閱 gtsvector_options()

sortsupport

回傳一個比較器函數,以以一種保留局部性的方式對資料進行排序。它由 CREATE INDEXREINDEX 命令使用。建立的索引的品質取決於比較器函數確定的排序順序在多大程度上保留了輸入的局部性。

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

TheSQL函數的宣告必須如下所示

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 將在處理每個 tuple 後重設。因此,不必擔心 pfree'ing 您 palloc 的所有內容。但是,在某些情況下,支援方法跨重複呼叫快取資料很有用。為此,請在 fcinfo->flinfo->fn_mcxt 中分配更長壽的資料,並在 fcinfo->flinfo->fn_extra 中保留指向它的指標。此類資料將在索引操作的生命週期內(例如,單個 GiST 索引掃描、索引建立或索引 tuple 插入)持續存在。替換 fn_extra 值時,請小心 pfree 先前的值,否則洩漏將在操作期間累積。

64.2.4. 實作 #

64.2.4.1. GiST 索引建立方法 #

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

只有當索引使用的每個 opclass 都提供 sortsupport 函數時,已排序方法才可用,如第 64.2.3 節中所述。如果它們提供,則此方法通常是最好的,因此預設情況下使用它。

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

已緩衝方法需要比簡單方法更頻繁地呼叫 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-tree 等效功能

cube

用於多維立方體的索引

hstore

用於儲存 (key, value) 配對的模組

intarray

用於 int4 值的一維陣列的 RD-Tree

ltree

用於樹狀結構的索引

pg_trgm

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

seg

用於float 範圍的索引

提交更正

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