支持的版本: 当前 (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代表广义搜索树。它是一种平衡的、树形结构的访问方法,充当实现任意索引方案的基础模板。B 树、R 树和许多其他索引方案都可以在GiST.

的优点之一是GiST它允许数据类型的领域专家开发具有适当访问方法的自定义数据类型,而不是数据库专家。

此处的一些信息来自加州大学伯克利分校的 GiST 索引项目网站和 Marcel Kornacker 的论文《下一代数据库系统的访问方法》。GiSTPostgreSQL 中的实现主要由 Teodor Sigaev 和 Oleg Bartunov 维护,他们的网站上有更多信息。

64.2.2. 内置操作符类 #

核心 PostgreSQL 发行版包含GiST表 64.1 中所示的操作符类。(附录 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. 可扩展性 #

传统上,实现新的索引访问方法意味着大量艰苦的工作。有必要了解数据库的内部工作原理,例如锁管理器和预写日志。但是,GiST接口具有很高的抽象级别,要求访问方法实现者仅实现被访问数据类型的语义。GiST层本身负责并发、日志记录和搜索树结构。

这种可扩展性不应与可以处理的数据方面其他标准搜索树的可扩展性相混淆。例如,PostgreSQL 支持可扩展的 B 树和哈希索引。这意味着您可以使用 PostgreSQL 在您想要的任何数据类型上构建 B 树或哈希。但是 B 树只支持范围谓词(<=>),而哈希索引只支持相等查询。

因此,如果您使用 PostgreSQL B 树为图像集合建立索引,您只能发出诸如图像 x 等于图像 y图像 x 小于图像 y图像 x 大于图像 y的查询。 根据您如何在此上下文中定义 等于小于大于,这可能会很有用。但是,通过使用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支持无损和有损索引结构。

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 声明对 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) 的数据类型。通过在此GiST支持方法中实现正确的 union 算法,很容易支持不是这种情况的数据类型。

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);
}

请注意,通过修改传入的 v 结构来传递 picksplit 函数的结果。返回值本身被忽略,尽管按照惯例会返回 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 值。(无穷大和负无穷大在内部用于处理诸如 null 之类的情况,因此不建议 distance 函数返回这些值。)

fetch

将数据项的压缩索引表示形式转换为原始数据类型,用于仅索引扫描。返回的数据必须是原始索引值的精确、无损副本。

SQL函数的声明必须如下所示

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

该参数是指向 GISTENTRY 结构的指针。在输入时,它的 key 字段包含一个压缩形式的非空叶子数据。返回值是另一个 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 函数。

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() 宏从其他支持函数访问这些选项。

下面给出了 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 函数将每个元组插入树中来构建索引,这会慢得多。

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 掉所有你 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

用于浮点范围的索引

提交更正

如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用此表单报告文档问题。