受支持版本: 16 / 15 / 14

62.3. B-树支持函数 #

Table 36.9所示,btree 定义了一个必需和五个可选的支持函数。六个用户定义的方法如下:

order

对于 btree 操作符族为其提供比较操作符的每一种数据类型组合,都必须提供一个比较支持函数。该函数在 pg_amproc 中注册,支持函数编号为 1,并且 amproclefttype/amprocrighttype 要等于该比较的左右数据类型(也就是与匹配操作符在 pg_amop 中注册时相同的数据类型)。比较函数必须接受两个非空值 AB,并返回一个 int32 值,其值为 < 00> 0,分别对应 A < BA = BA > B。不允许返回空值:该数据类型的所有值都必须可比较。示例见 src/backend/access/nbtree/nbtcompare.c

如果参与比较的值属于可排序数据类型,则会通过标准的 PG_GET_COLLATION() 机制,把适当的排序规则 OID 传递给比较支持函数。

sortsupport

可选地,btree 操作符族可以提供排序支持函数,注册为支持函数编号 2。这些函数允许以比朴素地直接调用比较支持函数更高效的方式,实现用于排序的比较。相关 API 定义在 src/include/utils/sortsupport.h 中。

in_range

可选地,btree 操作符族可以提供 in_range 支持函数,注册为支持函数编号 3。这些函数不会在 btree 索引操作期间使用;相反,它们会扩展操作符族的语义,使其能够支持包含 RANGE offset PRECEDINGRANGE offset FOLLOWING 这两种帧边界类型的窗口子句(见Section 4.2.8)。本质上,它额外提供的信息是:如何以与该族数据排序兼容的方式,对 offset 值进行加减。

in_range 函数必须具有如下签名:

in_range(val type1, base type1, offset type2, sub bool, less bool)
returns bool

valbase 必须具有相同类型,并且该类型必须是操作符族支持的类型之一(也就是该族为其提供排序的一种类型)。不过, offset 可以是另一种类型,而该类型甚至可能不被该族以其他方式支持。例如,内置的 time_ops 族就提供了一个 in_range 函数,其 offset 的类型为 interval。一个族可以提供 in_range 函数,分别对应其支持的任意类型以及一个或多个 offset 类型。每个 in_range 函数都应在 pg_amproc 中登记,其中 amproclefttype 等于 type1,而 amprocrighttype 等于 type2

in_range 函数的核心语义取决于这两个 Boolean 标志参数。它应当先对 baseoffset 做加法或减法,再把 val 与结果比较,具体如下:

  • 如果 !sub!less,返回 val >= (base + offset)

  • 如果 !subless,返回 val <= (base + offset)

  • 如果 sub!less,返回 val >= (base - offset)

  • 如果 subless,返回 val <= (base - offset)

在此之前,函数应检查 offset 的符号:如果它小于零,就引发错误 ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE (22013),错误文本类似于 invalid preceding or following size in window function。 (这是 SQL 标准的要求,尽管非标准操作符族或许会选择忽略这一限制,因为从语义上看似乎并没有太大必要。) 把这个要求委托给 in_range 函数,是为了让核心代码无需理解对于某个特定数据类型来说小于零究竟意味着什么。

另外还希望 in_range 函数在可行时避免因为 base + offsetbase - offset 会溢出而抛出错误。即便该值会超出该数据类型的取值范围,仍然可以判定正确的比较结果。注意,如果该数据类型包含诸如 infinityNaN 之类的概念,就可能需要格外小心,以确保 in_range 的结果与该操作符族的正常排序顺序一致。

in_range 函数的结果必须与操作符族施加的排序顺序保持一致。更精确地说,给定任意固定的 offsetsub 值,则:

  • 如果 in_rangeless = true 时,对某个 val1base 返回真,那么对于每一个 val2 <= val1,只要其 base 相同,它也必须返回真。

  • 如果 in_rangeless = true 时,对某个 val1base 返回假,那么对于每一个 val2 >= val1,只要其 base 相同,它也必须返回假。

  • 如果 in_rangeless = true 时,对某个 valbase1 返回真,那么对于每一个 base2 >= base1,只要其 val 相同,它也必须返回真。

  • 如果 in_rangeless = true 时,对某个 valbase1 返回假,那么对于每一个 base2 <= base1,只要其 val 相同,它也必须返回假。

less = false 时,也有条件相反的对应陈述成立。

如果被排序的类型(type1)属于可排序数据类型,则会通过标准的 PG_GET_COLLATION() 机制,把适当的排序规则 OID 传递给 in_range 函数。

in_range 函数不必处理 NULL 输入,并且通常会被标记为 strict。

equalimage

可选地,btree 操作符族可以提供 equalimage相等蕴含映像相等)支持函数,注册为支持函数编号 4。这些函数允许核心代码判断何时可以安全地应用 btree 去重优化。目前, equalimage 函数只会在构建或重建索引时被调用。

equalimage 函数必须具有如下签名:

equalimage(opcintype oid) returns bool

返回值是关于某个操作符类及其排序规则的静态信息。返回 true 表示:该操作符类的 order 函数被保证只有在返回 0arguments are equal)时,其 AB 参数才是可以互换而不损失任何语义信息的。如果未注册 equalimage 函数,或其返回 false,就表示不能假定该条件成立。

opcintype 参数是该操作符类所索引数据类型的 pg_type.oid。这只是为了方便在不同操作符类之间复用同一个底层 equalimage 函数。如果 opcintype 是可排序数据类型,则会通过标准的 PG_GET_COLLATION() 机制,把适当的排序规则 OID 传递给 equalimage 函数。

就操作符类本身而言,返回 true 表示可以安全去重(或者更准确地说,对于传递给其 equalimage 函数的那个排序规则来说可以安全去重)。但是,只有当每个被索引列都使用注册了 equalimage 函数的操作符类,并且各函数在调用时实际都返回 true 时,核心代码才会认为某个索引可以安全地去重。

映像相等与简单的按位相等几乎是同一个条件。两者有一个细微差别:当 varlena 数据类型建立索引时,由于输入时应用 TOAST 压缩可能不一致,两个映像相等的 datum 的磁盘表示未必按位相等。形式化地说,当某个操作符类的 equalimage 函数返回 true 时,就可以安全地假定 datum_image_eq() C 函数将始终与该操作符类的 order 函数保持一致 (前提是向 equalimageorder 函数传入的是同一个排序规则 OID)。

核心代码本质上无法仅根据同一多数据类型族中其他操作符类的细节,推断出某个操作符类的相等蕴含映像相等状态。此外,操作符族注册跨类型的 equalimage 函数并不合理,尝试这样做会导致错误。原因在于,相等蕴含映像相等状态并不只依赖于排序/相等语义,而这些语义大致是在操作符族层面定义的。一般来说,某个具体数据类型所实现的语义必须单独考虑。

核心 PostgreSQL 发行版中包含的操作符类遵循的惯例是:注册一个现成的通用 equalimage 函数。大多数操作符类注册 btequalimage(),这表示去重在无条件下都是安全的。像 text 这样可排序数据类型的操作符类会注册 btvarstrequalimage(),这表示在确定性排序规则下去重是安全的。第三方扩展的最佳实践则是注册它们自己的自定义函数,以保留控制权。

options

可选地,B-树操作符族可以提供 options操作符类特定选项)支持函数,注册为支持函数编号 5。这些函数定义一组用户可见的参数,用于控制操作符类行为。

options 支持函数必须具有如下签名:

options(relopts local_relopts *) returns void

该函数会接收一个指向 local_relopts 结构的指针,必须在其中填入一组操作符类特定选项。其他支持函数可以使用 PG_HAS_OPCLASS_OPTIONS()PG_GET_OPCLASS_OPTIONS() 宏来访问这些选项。

目前还没有任何 B-树操作符类带有 options 支持函数。B-树不像 GiST、SP-GiST、GIN 和 BRIN 那样允许键具有灵活的表示方式。因此, options 在当前的 B-树 索引访问方法中大概没有太多用途。尽管如此,为了统一性,这个支持函数还是被加入到了 B-树中,并且很可能会在 PostgreSQL 中 B-树 的进一步演进过程中派上用场。

skipsupport

可选地,btree 操作符族可以提供skip 支持函数,注册为支持函数编号 6。这些函数为 B-树 代码提供了一种按键空间顺序遍历某个操作符类底层输入类型所能表示的全部可能值的方法。当核心代码应用跳过扫描优化时,就会用到它。相关 API 定义在 src/include/utils/skipsupport.h 中。

没有提供 skip 支持函数的操作符类,仍然可以使用跳过扫描。核心代码仍可采用其后备策略,尽管对于某些离散类型来说,这种策略可能并非最优。对于连续类型上的操作符类,提供 skip 支持函数通常没有意义(甚至可能不可行)。

操作符族注册跨类型的 skipsupport 函数并不合理,尝试这样做会导致错误。因为要确定下一个可被索引的值,必须通过递增一个从索引元组复制出来的值来完成。所生成的值都必须属于同一种底层数据类型(也就是被跳过的索引列的 opclass 输入类型)。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。