GiST代表广义搜索树。它是一种平衡的、树状结构的访问方法,充当实现任意索引方案的基础模板。B 树、R 树和许多其他索引方案都可以在GiST.
的一个优点是GiST它允许由数据类型的领域专家,而不是数据库专家,开发具有适当访问方法的自定义数据类型。
这里的一些信息来源于加州大学伯克利分校的 GiST 索引项目 网站和 Marcel Kornacker 的论文 下一代数据库系统的访问方法。PostgreSQL 中的GiST实现主要由 Teodor Sigaev 和 Oleg Bartunov 维护,更多信息请访问他们的网站。
核心 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
操作符类不是 inet
和 cidr
类型的默认类。要使用它,请在 CREATE INDEX
中提及类名,例如
CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);
传统上,实现新的索引访问方法意味着大量困难的工作。有必要了解数据库的内部工作原理,例如锁管理器和预写日志。GiST接口具有很高的抽象级别,仅要求访问方法实现者实现被访问的数据类型的语义。GiST层本身负责并发、日志记录和搜索树结构。
这种可扩展性不应与可以处理的数据方面的其他标准搜索树的可扩展性相混淆。例如,PostgreSQL 支持可扩展的 B 树和哈希索引。这意味着您可以使用 PostgreSQL 在任何您想要的数据类型上构建 B 树或哈希。但是 B 树仅支持范围谓词 (<
、=
、>
),而哈希索引仅支持相等查询。
因此,如果您使用 PostgreSQL B 树索引图像集合,您只能发出诸如“图像 x 等于图像 y”、“图像 x 小于图像 y”和“图像 x 大于图像 y”之类的查询。根据您如何在此上下文中定义“等于”、“小于”和“大于”,这可能会很有用。但是,通过使用GiST基于索引,您可以创建提出特定领域问题的方法,例如“查找所有马的图像”或“查找所有曝光过度的图像”。
要启动并运行GiST访问方法,只需实现几个用户定义的方法,这些方法定义树中键的行为。当然,这些方法必须非常精妙才能支持精细查询,但对于所有标准查询(B 树、R 树等),它们都相对简单。简而言之,GiST结合了可扩展性、通用性、代码重用和干净的接口。
有五种方法是GiST必须提供的方法有六个,可选的方法有六个。索引的正确性由正确实现的 same
、consistent
和 union
方法保证,而索引的效率(大小和速度)将取决于 penalty
和 picksplit
方法。两个可选方法是 compress
和 decompress
,它们允许索引的内部树数据类型与它索引的数据类型不同。叶节点应该是被索引的数据类型,而其他树节点可以是任何 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)
。通过在此中实现正确的 union 算法,很容易支持不属于这种情况的数据类型GiST支持方法。
union
函数的结果必须是索引的存储类型的值,无论它是什么(它可能与也可能不与索引列的类型不同)。union
函数应返回指向新 palloc()
ed 内存的指针。您不能只是按原样返回输入值,即使没有类型更改。
如上所示,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
函数对于索引的良好性能至关重要。设计合适的 penalty
和 picksplit
实现是实现高性能的挑战所在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
,则原始排序操作符的返回类型必须为 float8
或 float4
,并且距离函数的结果值必须与原始排序操作符的结果值具有可比性,因为执行器将使用距离函数结果和重新计算的排序操作符结果进行排序。否则,只要结果值的相对顺序与排序操作符返回的顺序匹配,距离函数的结果值可以是任何有限的 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 */ }
由于键的表示形式在GiST是灵活的,它可能取决于用户指定的参数。例如,可以指定密钥签名的长度。请参阅 gtsvector_options()
示例。
排序支持
返回一个比较器函数,用于以保留局部性的方式对数据进行排序。它被 CREATE INDEX
和 REINDEX
命令使用。创建的索引的质量取决于比较器函数确定的排序顺序保留输入局部性的程度。
sortsupport
方法是可选的。如果未提供,则 CREATE INDEX
会使用 penalty
和 picksplit
函数通过将每个元组插入到树中来构建索引,这会慢得多。
该SQL函数的声明必须如下所示
CREATE OR REPLACE FUNCTION my_sortsupport(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
参数是指向 SortSupport
结构体的指针。至少,该函数必须填写其比较器字段。比较器接受三个参数:两个要比较的 Datum,以及指向 SortSupport
结构体的指针。Datum 是以索引中存储的格式(即 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 上一个值,否则泄漏将在操作期间累积。
构建 GiST 索引的最简单方法就是逐个插入所有条目。对于大型索引,这往往很慢,因为如果索引元组分散在索引中,并且索引足够大而无法放入缓存,则需要大量的随机 I/O。PostgreSQL 支持两种用于 GiST 索引初始构建的替代方法:排序和缓冲模式。
只有当索引使用的每个操作类都提供 第 64.2.3 节中所述的 sortsupport
函数时,排序方法才可用。如果它们提供,则此方法通常是最好的,因此默认使用它。
缓冲方法的工作方式是,不立即将元组直接插入索引中。它可以显着减少非有序数据集所需的随机 I/O 量。对于有序数据集,好处较小或不存在,因为一次只有少量页面接收新元组,并且即使索引整体不适合缓存,这些页面也适合缓存。
缓冲方法需要比简单方法更频繁地调用 penalty
函数,这会消耗一些额外的 CPU 资源。此外,缓冲区需要临时的磁盘空间,大小可达生成索引的大小。缓冲还会以积极和消极的方向影响生成的索引的质量。这种影响取决于各种因素,例如输入数据的分布和操作类实现。
如果无法进行排序,则默认情况下,当索引大小达到 effective_cache_size 时,GiST 索引构建会切换到缓冲方法。可以通过 CREATE INDEX 命令的 buffering
参数手动强制或阻止缓冲。默认行为适用于大多数情况,但如果输入数据是有序的,则关闭缓冲可能会稍微加快构建速度。
PostgreSQL 源代码发行版包含使用GiST实现的索引方法的几个示例。核心系统当前提供文本搜索支持(对 tsvector
和 tsquery
进行索引),以及某些内置几何数据类型的 R 树等效功能(请参阅 src/backend/access/gist/gistproc.c
)。以下 contrib
模块还包含GiST操作类
btree_gist
多种数据类型的 B 树等效功能
cube
多维立方体的索引
hstore
用于存储 (键, 值) 对的模块
intarray
一维 int4 值数组的 RD 树
ltree
类树结构的索引
pg_trgm
使用三元组匹配的文本相似度
seg
“浮点范围”的索引
如果您在文档中发现任何不正确、与您使用特定功能的经验不符或需要进一步说明的内容,请使用此表格报告文档问题。