## 65.3.可扩展性 传统上,实现一种新的索引访问方法意味着很多困难的工作。有必要了解数据库的内部工作原理,例如锁管理器和提前写入日志。GiST接口具有很高的抽象级别,只需要访问方法实现者实现所访问的数据类型的语义。GiST层本身负责并发、记录和搜索树结构。 这种可扩展性不应与其他标准搜索树在可处理数据方面的可扩展性相混淆。例如,PostgreSQL支持可扩展的B树和散列索引。这意味着您可以使用PostgreSQL在任何想要的数据类型上构建B树或散列。但是B-树只支持范围谓词(`<`,`=`,`>`),哈希索引只支持相等查询。 因此,如果使用PostgreSQL B树为图像集合编制索引,则只能发出诸如“imagex是否等于imagey”、“imagex是否小于imagey”和“imagex是否大于imagey”之类的查询。根据您在本文中如何定义“等于”、“小于”和“大于”,这可能会很有用。然而,通过使用基于要点的索引,您可以创建方法来询问特定领域的问题,可能是“查找所有马的图像”或“查找所有暴露的图像”。 启动并运行GiST访问方法只需实现几个用户定义的方法,这些方法定义树中键的行为。当然,这些方法必须非常花哨,才能支持花哨的查询,但对于所有标准查询(B树、R树等),它们都相对简单。简而言之,GiST结合了可扩展性、通用性、代码重用和干净的接口。 GiST的索引运算符类必须提供五种方法,六种是可选的。索引的正确性通过正确执行`相同的`,`一致的`和`协会`方法,而索引的效率(大小和速度)将取决于`处罚`和`皮克斯普利特`方法。有两种可选方法:`压紧`和`减压`,它允许索引具有与其索引的数据不同类型的内部树数据。叶子应该是索引数据类型,而其他树节点可以是任何C结构(但您仍然必须遵循这里的PostgreSQL数据类型规则,请参阅关于)`变长`用于可变大小的数据)。如果树的内部数据类型存在于SQL级别,则`存储`选择`创建操作符类`可以使用命令。可选的第八种方法是`距离`,如果operator类希望支持有序扫描(最近邻搜索),则需要该选项。可选的第九种方法`取来`如果operator类希望支持仅索引扫描,则需要`压紧`方法被省略。可选的第十种方法`选项`如果运算符类具有用户指定的参数,则需要。可选的第十一种方法`sortsupport`用于加快建立要点索引。 `一致的` 给定一个索引项`p`和一个查询值`q`,此函数确定索引项是否与查询“一致”;也就是说,谓词可以“*`索引列`* *`可转位算子`* `q`“对于由索引项表示的任何行,是否为真?”?对于叶索引条目,这相当于测试可索引条件,而对于内部树节点,这决定是否需要扫描树节点表示的索引的子树。当结果是`符合事实的`A.`复查`国旗也必须归还。这表明谓词是肯定为真还是仅可能为真。如果`复查` = `错误的`然后索引准确地测试了谓词条件,而如果`复查` = `符合事实的`这一排只是一场候选赛。在这种情况下,系统将自动评估*`可转位算子`*对照实际行值,查看它是否真的匹配。这种约定允许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); } ``` 在这里`钥匙`是索引中的一个元素,并且`查询`在索引中查找的值。这个`战略数字`参数指示要应用的运算符类中的哪个运算符-它与`创建操作符类`命令 根据类中包含的运算符,类的数据类型`查询`可能因运算符而异,因为它将是运算符右侧的任何类型,这可能不同于左侧显示的索引数据类型。(上面的代码框架假设只有一种类型是可能的;如果不是,则获取`查询`参数值必须取决于运算符。)建议`一致的`函数将opclass的索引数据类型用于`查询`参数,即使实际类型可能是其他类型,具体取决于运算符。 `协会` 此方法整合树中的信息。给定一组条目,此函数将生成一个表示所有给定条目的新索引条目。 函数的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); } ``` 如您所见,在这个框架中,我们处理的是一种数据类型`并集(X,Y,Z)=并集(并集(X,Y,Z)`。通过在这个GiST支持方法中实现适当的并集算法,在不支持这种情况的情况下,支持数据类型是很容易的。 调查的结果`协会`函数必须是索引的存储类型的值,不管它是什么(它可能不同于索引列的类型,也可能不同于索引列的类型)。这个`协会`函数应该返回一个指向新函数的指针`帕洛克()`我忘记了。即使没有类型更改,也不能按原样返回输入值。 如上图所示`协会`功能第一`内部的`争论实际上是一场争论`GistEntryVector`指针。第二个参数是指向整数变量的指针,可以忽略它。(过去要求`协会`函数将其结果值的大小存储到该变量中,但这不再是必需的。) `压紧` 将数据项转换为适合索引页中物理存储的格式。如果`压紧`方法,数据项存储在索引中,无需修改。 函数的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); } ``` 你必须适应*`压缩数据类型`*当然,为了压缩叶节点,需要转换到特定的类型。 `减压` 将存储的数据项表示形式转换为可由运算符类中的其他GiST方法操作的格式。如果`减压`方法,假定其他GiST方法可以直接处理存储的数据格式。(`减压`不一定是相反的`压紧`方法特别是,如果`压紧`是有损的那就不可能了`减压`准确地重建原始数据。`减压`不一定等同于`取来`,因为其他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)); } ``` 上述骨架适用于不需要减压的情况。(当然,完全省略该方法更容易,在这种情况下建议这样做。) `处罚` 返回一个值,该值指示将新条目插入树的特定分支的“成本”。项目将沿最短路径插入`处罚`在树上。返回的值`处罚`应该是非负的。如果返回负值,它将被视为零。 函数的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); } ``` 出于历史原因`处罚`函数不仅仅返回`浮动`后果相反,它必须将值存储在第三个参数指示的位置。返回值本身被忽略,尽管通常会传回该参数的地址。 这个`处罚`函数对于索引的良好性能至关重要。它将在插入时用于确定在树中选择新条目的添加位置时遵循哪个分支。在查询时,索引越平衡,查找速度就越快。 `皮克斯普利特` 当需要拆分索引页时,此函数决定页上的哪些条目将保留在旧页上,哪些条目将移动到新页。 函数的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); } ``` 请注意`皮克斯普利特`函数的结果是通过修改传入的`五、`结构返回值本身被忽略,尽管传回`五、`. 喜欢`处罚`这个`皮克斯普利特`函数对于索引的良好性能至关重要。设计合适的`处罚`和`皮克斯普利特`实现是实现性能良好的GiST索引的挑战所在。 `相同的` 如果两个索引项相同,则返回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); } ``` 出于历史原因`相同的`函数不仅返回布尔结果;相反,它必须将标志存储在第三个参数指示的位置。返回值本身被忽略,尽管通常会传回该参数的地址。 `距离` 给定一个索引项`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); } ``` 争论的焦点`距离`函数的参数与`一致的`作用 在确定距离时,只要结果永远不大于条目的实际距离,就允许使用某种近似值。因此,例如,在几何应用中,到边界框的距离通常是足够的。对于内部树节点,返回的距离不得大于到任何子节点的距离。如果返回的距离不准确,则必须设置该函数`*复查`这是真的。(对于内部树节点,这不是必需的;对于它们,计算总是假定不精确。)在这种情况下,执行器将在从堆中获取元组后计算准确的距离,并在必要时对元组重新排序。 如果距离函数返回`*重新检查=正确`对于任何叶节点,原始排序运算符的返回类型必须为`浮动8`或`浮动4`,距离函数的结果值必须与原始排序运算符的结果值相比较,因为执行器将使用距离函数结果和重新计算的排序运算符结果进行排序。否则,距离函数的结果值可以是任意有限的`浮动8`值,只要结果值的相对顺序与排序运算符返回的顺序匹配。(无穷大和负无穷大在内部用于处理null等情况,因此不建议`距离`函数返回这些值。) `取来` 将数据项的压缩索引表示形式转换为原始数据类型(仅用于索引扫描)。返回的数据必须是原始索引值的准确无损副本。 函数的SQL声明必须如下所示: ``` CREATE OR REPLACE FUNCTION my_fetch(internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT; ``` 该参数是指向`GISTENTRY`结构。进入时,其`钥匙`字段包含压缩形式的非空叶数据。返回值是另一个`GISTENTRY`结构,谁的`钥匙`字段包含原始未压缩形式的相同数据。如果opclass的compress函数对叶条目不做任何处理,则`取来`方法可以按原样返回参数。或者,如果opclass没有压缩函数,则`取来`方法也可以省略,因为它必然是不可操作的。 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方法对叶条目有损,则operator类不能支持仅索引扫描,并且不能定义`取来`作用 `选项` 允许定义控制运算符类行为的用户可见参数。 函数的SQL声明必须如下所示: ``` CREATE OR REPLACE FUNCTION my_options(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; ``` 函数被传递一个指向`本地重新选择`struct,它需要填充一组特定于运算符类的选项。可以使用从其他支持功能访问这些选项`PG_有_OPCLASS_选项()`和`PG_GET_OPCLASS_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_选项()`例如 `sortsupport` 返回一个比较器函数,以保留局部性的方式对数据进行排序。它是由`创建索引`和`重新索引`命令。创建的索引的质量取决于比较器函数确定的排序顺序在多大程度上保留了输入的局部性。 这个`sortsupport`方法是可选的。如果没有提供,`创建索引`通过使用`处罚`和`皮克斯普利特`功能,这要慢得多。 函数的SQL声明必须如下所示: ``` CREATE OR REPLACE FUNCTION my_sortsupport(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; ``` 该参数是指向`SortSupport`结构。至少,函数必须填写其comparator字段。比较器有三个参数:两个要比较的基准和一个指向`SortSupport`结构。基准是两个索引值,其格式与索引中存储的格式相同;也就是说,以`压紧`方法完整的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`将在处理每个元组后重置。因此,担心自己失去的一切并不重要。然而,在某些情况下,支持方法在重复调用中缓存数据是有用的。要做到这一点,请在中分配寿命较长的数据`fcinfo->flinfo->fn_mcxt`,并在中保留指向它的指针`fcinfo->flinfo->fn_额外`。此类数据将在索引操作的整个生命周期内(例如,单个GiST索引扫描、索引构建或索引元组插入)保持有效。更换时,请小心释放以前的值`fn_额外费用`值,否则泄漏将在操作期间累积。