到目前为止描述的过程使您能够定义新的类型、新的函数和新的操作符。但是,我们还不能在新数据类型的列上定义索引。为此,我们必须为新的数据类型定义一个操作符类。在本节的后面,我们将通过一个示例来说明这个概念:为 B 树索引方法定义一个新的操作符类,该方法以升序的绝对值顺序存储和排序复数。
操作符类可以分组为操作符族,以显示语义兼容的类之间的关系。当只涉及一个数据类型时,操作符类就足够了,所以我们将首先关注这种情况,然后再回到操作符族。
操作符类与索引访问方法相关联,例如B 树或GIN。可以使用CREATE ACCESS METHOD定义自定义索引访问方法。有关详细信息,请参阅第 62 章。
索引方法的例程不直接了解索引方法将操作的数据类型。相反,一个操作符类标识索引方法需要使用的操作集,以便使用特定的数据类型。操作符类之所以这样称呼,是因为它们指定的一件事是可以与索引一起使用的WHERE
子句操作符集(即,可以转换为索引扫描限定符)。操作符类还可以指定索引方法的内部操作所需的一些支持函数,这些函数不直接对应于可以与索引一起使用的任何WHERE
子句操作符。
可以为相同的数据类型和索引方法定义多个操作符类。通过这样做,可以为单个数据类型定义多组索引语义。例如,B 树索引需要为它所操作的每个数据类型定义一个排序顺序。对于一个复数数据类型,拥有一个 B 树操作符类按复数的绝对值排序数据、另一个按实部排序等等可能很有用。通常,其中一个操作符类将被认为是最常用的,并且将被标记为该数据类型和索引方法的默认操作符类。
相同的操作符类名称可以用于几种不同的索引方法(例如,B 树和哈希索引方法都有名为int4_ops
的操作符类),但是每个这样的类都是一个独立的实体,必须单独定义。
与操作符类关联的操作符由“策略编号”标识,这些编号用于标识每个操作符在其操作符类上下文中的语义。例如,B 树对键强制执行严格的排序,从小到大,因此诸如“小于”和“大于或等于”之类的操作符在 B 树中很有意义。因为PostgreSQL允许用户定义操作符,PostgreSQL无法查看操作符的名称(例如,<
或>=
)并判断它是什么类型的比较。相反,索引方法定义了一组“策略”,可以将其视为广义的操作符。每个操作符类指定对于特定数据类型和索引语义的解释,哪个实际操作符对应于每个策略。
B 树索引方法定义了五种策略,如表 36.3所示。
表 36.3. B 树策略
操作 | 策略编号 |
---|---|
小于 | 1 |
小于或等于 | 2 |
等于 | 3 |
大于或等于 | 4 |
大于 | 5 |
哈希索引仅支持相等性比较,因此它们只使用一种策略,如表 36.4所示。
表 36.4. 哈希策略
操作 | 策略编号 |
---|---|
等于 | 1 |
GiST 索引更灵活:它们根本没有固定的策略集。相反,每个特定的 GiST 操作符类的“一致性”支持例程会以其喜欢的方式解释策略编号。例如,几个内置的 GiST 索引操作符类对二维几何对象进行索引,提供如表 36.5所示的“R 树”策略。其中四个是真正的二维测试(重叠、相同、包含、被包含);其中四个仅考虑 X 方向;另外四个在 Y 方向上提供相同的测试。
表 36.5. GiST 二维“R 树”策略
操作 | 策略编号 |
---|---|
严格位于左侧 | 1 |
不向右扩展 | 2 |
重叠 | 3 |
不向左扩展 | 4 |
严格位于右侧 | 5 |
相同 | 6 |
包含 | 7 |
被包含 | 8 |
不向上扩展 | 9 |
严格位于下方 | 10 |
严格位于上方 | 11 |
不向下扩展 | 12 |
SP-GiST 索引在灵活性方面类似于 GiST 索引:它们没有固定的策略集。相反,每个操作符类的支持例程根据操作符类的定义解释策略编号。例如,用于点的内置操作符类使用的策略编号如表 36.6所示。
表 36.6. SP-GiST 点策略
操作 | 策略编号 |
---|---|
严格位于左侧 | 1 |
严格位于右侧 | 5 |
相同 | 6 |
被包含 | 8 |
严格位于下方 | 10 |
严格位于上方 | 11 |
GIN 索引类似于 GiST 和 SP-GiST 索引,因为它们也没有固定的策略集。相反,每个操作符类的支持例程根据操作符类的定义解释策略编号。例如,用于数组的内置操作符类使用的策略编号如表 36.7所示。
表 36.7. GIN 数组策略
操作 | 策略编号 |
---|---|
重叠 | 1 |
包含 | 2 |
被包含 | 3 |
等于 | 4 |
BRIN 索引与 GiST、SP-GiST 和 GIN 索引类似,它们也没有固定的策略集。相反,每个操作符类的支持例程根据操作符类的定义来解释策略编号。例如,内置 Minmax
操作符类使用的策略编号如表 36.8所示。
表 36.8. BRIN Minmax 策略
操作 | 策略编号 |
---|---|
小于 | 1 |
小于或等于 | 2 |
等于 | 3 |
大于或等于 | 4 |
大于 | 5 |
请注意,上面列出的所有操作符都返回布尔值。实际上,所有定义为索引方法搜索操作符的操作符都必须返回 boolean
类型,因为它们必须出现在 WHERE
子句的顶层才能与索引一起使用。(某些索引访问方法还支持排序操作符,通常不返回布尔值;该功能在第 36.16.7 节中讨论。)
策略通常不足以让系统了解如何使用索引。实际上,索引方法需要额外的支持例程才能工作。例如,B 树索引方法必须能够比较两个键,并确定一个键是否大于、等于或小于另一个键。类似地,哈希索引方法必须能够计算键值的哈希码。这些操作不对应于 SQL 命令中限定符中使用的操作符;它们是索引方法内部使用的管理例程。
与策略一样,操作符类标识了对于给定的数据类型和语义解释,哪些特定函数应该扮演这些角色。索引方法定义了它需要的函数集,操作符类通过将它们分配给索引方法指定的“支持函数编号”来标识要使用的正确函数。
此外,一些操作符类允许用户指定控制其行为的参数。每个内置索引访问方法都有一个可选的 options
支持函数,该函数定义了一组特定于操作符类的参数。
B 树需要一个比较支持函数,并允许在操作符类作者选择的情况下提供四个额外的支持函数,如表 36.9所示。这些支持函数的要求在第 64.1.3 节中进一步解释。
表 36.9. B 树支持函数
函数 | 支持编号 |
---|---|
比较两个键并返回一个整数,该整数小于零、零或大于零,表示第一个键小于、等于或大于第二个键 | 1 |
返回 C 可调用排序支持函数的地址(可选) | 2 |
将测试值与基值加上/减去偏移量进行比较,并根据比较结果返回 true 或 false(可选) | 3 |
确定使用该操作符类的索引是否可以安全地应用 btree 去重优化(可选) | 4 |
定义特定于此操作符类的选项(可选) | 5 |
哈希索引需要一个支持函数,并允许在操作符类作者选择的情况下提供两个额外的支持函数,如表 36.10所示。
表 36.10. 哈希支持函数
函数 | 支持编号 |
---|---|
计算键的 32 位哈希值 | 1 |
计算给定 64 位盐的键的 64 位哈希值;如果盐为 0,则结果的低 32 位必须与函数 1 计算的值匹配(可选) | 2 |
定义特定于此操作符类的选项(可选) | 3 |
GiST 索引有 11 个支持函数,其中 6 个是可选的,如表 36.11所示。(更多信息请参阅第 64.2 节。)
表 36.11. GiST 支持函数
函数 | 描述 | 支持编号 |
---|---|---|
consistent |
确定键是否满足查询限定符 | 1 |
union |
计算一组键的并集 | 2 |
compress |
计算要索引的键或值的压缩表示形式(可选) | 3 |
decompress |
计算压缩键的解压缩表示形式(可选) | 4 |
penalty |
计算将新键插入具有给定子树键的子树的惩罚 | 5 |
picksplit |
确定页面的哪些条目要移动到新页面,并计算结果页面的并集键 | 6 |
相同 |
比较两个键,如果它们相等则返回 true | 7 |
distance |
确定键到查询值的距离(可选) | 8 |
fetch |
计算仅索引扫描的压缩键的原始表示形式(可选) | 9 |
options |
定义特定于此操作符类的选项(可选) | 10 |
sortsupport |
提供在快速索引构建中使用的排序比较器(可选) | 11 |
SP-GiST 索引有 6 个支持函数,其中一个是可选的,如表 36.12所示。(更多信息请参阅第 64.3 节。)
表 36.12. SP-GiST 支持函数
函数 | 描述 | 支持编号 |
---|---|---|
config |
提供有关操作符类的基本信息 | 1 |
choose |
确定如何将新值插入到内部元组中 | 2 |
picksplit |
确定如何对一组值进行分区 | 3 |
inner_consistent |
确定查询需要搜索哪些子分区 | 4 |
leaf_consistent |
确定键是否满足查询限定符 | 5 |
options |
定义特定于此操作符类的选项(可选) | 6 |
GIN 索引有 7 个支持函数,其中 4 个是可选的,如表 36.13所示。(更多信息请参阅第 64.4 节。)
表 36.13. GIN 支持函数
函数 | 描述 | 支持编号 |
---|---|---|
compare |
比较两个键并返回一个整数,该整数小于零、零或大于零,表示第一个键小于、等于或大于第二个键 | 1 |
extractValue |
从要索引的值中提取键 | 2 |
extractQuery |
从查询条件中提取键 | 3 |
consistent |
确定值是否与查询条件匹配(布尔变体)(如果存在支持函数 6,则为可选) | 4 |
comparePartial |
比较查询中的部分键和索引中的键,并返回一个整数,该整数小于零、零或大于零,表示 GIN 是否应该忽略此索引条目、将该条目视为匹配或停止索引扫描(可选) | 5 |
triConsistent |
确定值是否与查询条件匹配(三元变体)(如果存在支持函数 4,则为可选) | 6 |
options |
定义特定于此操作符类的选项(可选) | 7 |
BRIN 索引有 5 个基本支持函数,其中一个是可选的,如表 36.14所示。某些版本的基本函数需要提供额外的支持函数。(更多信息请参阅第 64.5.3 节。)
表 36.14. BRIN 支持函数
函数 | 描述 | 支持编号 |
---|---|---|
opcInfo |
返回描述索引列的摘要数据的内部信息 | 1 |
add_value |
将新值添加到现有摘要索引元组 | 2 |
consistent |
确定值是否与查询条件匹配 | 3 |
union |
计算两个摘要元组的并集 | 4 |
options |
定义特定于此操作符类的选项(可选) | 5 |
与搜索操作符不同,支持函数返回特定索引方法期望的任何数据类型;例如,对于 B 树的比较函数,返回一个有符号整数。每个支持函数的参数数量和类型也取决于索引方法。对于 B 树和哈希,比较和哈希支持函数采用与操作符类中包含的操作符相同的输入数据类型,但对于大多数 GiST、SP-GiST、GIN 和 BRIN 支持函数,情况并非如此。
现在我们已经了解了这些概念,下面是创建新操作符类的示例。(您可以在源代码发行版中的 src/tutorial/complex.c
和 src/tutorial/complex.sql
中找到此示例的工作副本。)操作符类封装了按绝对值顺序对复数进行排序的操作符,因此我们选择名称 complex_abs_ops
。首先,我们需要一组操作符。定义操作符的过程在第 36.14 节中讨论过。对于 B 树上的操作符类,我们需要的操作符是
定义一组相关的比较操作符的最不容易出错的方法是先编写 B 树比较支持函数,然后将其他函数编写为围绕支持函数的一行包装器。这减少了在极端情况下获得不一致结果的可能性。按照这种方法,我们首先编写
#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y) static int complex_abs_cmp_internal(Complex *a, Complex *b) { double amag = Mag(a), bmag = Mag(b); if (amag < bmag) return -1; if (amag > bmag) return 1; return 0; }
现在小于函数看起来像
PG_FUNCTION_INFO_V1(complex_abs_lt); Datum complex_abs_lt(PG_FUNCTION_ARGS) { Complex *a = (Complex *) PG_GETARG_POINTER(0); Complex *b = (Complex *) PG_GETARG_POINTER(1); PG_RETURN_BOOL(complex_abs_cmp_internal(a, b) < 0); }
其他四个函数仅在它们如何将内部函数的结果与零进行比较方面有所不同。
接下来,我们向 SQL 声明基于这些函数的函数和操作符
CREATE FUNCTION complex_abs_lt(complex, complex) RETURNS bool
AS 'filename
', 'complex_abs_lt'
LANGUAGE C IMMUTABLE STRICT;
CREATE OPERATOR < (
leftarg = complex, rightarg = complex, procedure = complex_abs_lt,
commutator = > , negator = >= ,
restrict = scalarltsel, join = scalarltjoinsel
);
指定正确的交换操作符和否定操作符以及合适的限制和连接选择性函数非常重要,否则优化器将无法有效地使用索引。
这里还发生其他值得注意的事情
只能有一个名为,例如,=
并且对两个操作数都采用 complex
类型的操作符。在这种情况下,我们没有其他用于 complex
的操作符 =
,但是如果我们构建一个实用的数据类型,我们可能希望 =
成为复数的普通相等操作(而不是绝对值的相等)。在这种情况下,我们需要为 complex_abs_eq
使用其他操作符名称。
尽管 PostgreSQL 可以处理具有相同 SQL 名称的函数,只要它们具有不同的参数数据类型,但 C 只能处理一个具有给定名称的全局函数。因此,我们不应该将 C 函数命名为像 abs_eq
这样简单的名称。通常,最好在 C 函数名称中包含数据类型名称,以免与其他数据类型的函数冲突。
我们可以将该函数的 SQL 名称设为 abs_eq
,依靠 PostgreSQL 通过参数数据类型将它与任何其他同名的 SQL 函数区分开来。为了使示例简单,我们使函数在 C 级别和 SQL 级别具有相同的名称。
下一步是注册 B 树所需的支持例程。实现此功能的示例 C 代码位于包含操作符函数的同一文件中。这是我们声明函数的方式
CREATE FUNCTION complex_abs_cmp(complex, complex)
RETURNS integer
AS 'filename
'
LANGUAGE C IMMUTABLE STRICT;
现在我们有了所需的操作符和支持例程,我们终于可以创建操作符类了
CREATE OPERATOR CLASS complex_abs_ops DEFAULT FOR TYPE complex USING btree AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 complex_abs_cmp(complex, complex);
我们完成了!现在应该可以在 complex
列上创建和使用 B 树索引。
我们可以更冗长地编写操作符条目,例如
OPERATOR 1 < (complex, complex) ,
但是,当操作符采用我们为其定义操作符类的数据类型时,没有必要这样做。
上面的示例假设您想使此新操作符类成为 complex
数据类型的默认 B 树操作符类。如果您不想这样做,只需省略单词 DEFAULT
即可。
到目前为止,我们隐含地假设一个操作符类只处理一种数据类型。虽然在特定的索引列中肯定只有一种数据类型,但索引操作将索引列与不同数据类型的值进行比较通常很有用。此外,如果一个跨数据类型的操作符与一个操作符类一起使用,通常情况下,另一种数据类型也有其自己的相关操作符类。显式地建立相关类之间的联系很有帮助,因为这可以帮助规划器优化 SQL 查询(特别是对于 B 树操作符类,因为规划器包含大量关于如何使用它们的知识)。
为了满足这些需求,PostgreSQL 使用了操作符族的概念。一个操作符族包含一个或多个操作符类,还可以包含属于整个族但不属于族中任何单个类的可索引操作符和相应的支持函数。我们说这样的操作符和函数在该族中是“松散的”,而不是绑定到特定的类中。通常,每个操作符类都包含单数据类型的操作符,而跨数据类型的操作符在族中是松散的。
操作符族中的所有操作符和函数都必须具有兼容的语义,其中兼容性要求由索引方法设置。您可能会因此想知道为什么还要将族的特定子集单独挑出来作为操作符类;实际上,对于许多目的来说,类的划分是无关紧要的,族是唯一有趣的组。定义操作符类的原因是它们指定了支持任何特定索引需要多少族的内容。如果存在使用操作符类的索引,则该操作符类不能在不删除索引的情况下删除 - 但操作符族的其他部分,即其他操作符类和松散的操作符,可以被删除。因此,应该指定一个操作符类,以包含处理特定数据类型索引所需的最小操作符和函数集,然后可以将相关但非必要的操作符添加为操作符族的松散成员。
例如,PostgreSQL 有一个内置的 B 树操作符族 integer_ops
,其中包括操作符类 int8_ops
、int4_ops
和 int2_ops
,分别用于 bigint
(int8
)、integer
(int4
) 和 smallint
(int2
) 列上的索引。该族还包含允许比较这些类型中任意两种类型的跨数据类型比较操作符,因此可以使用另一种类型的比较值搜索其中一种类型的索引。可以通过以下定义复制该族
CREATE OPERATOR FAMILY integer_ops USING btree; CREATE OPERATOR CLASS int8_ops DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS -- standard int8 comparisons OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint8cmp(int8, int8) , FUNCTION 2 btint8sortsupport(internal) , FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) , FUNCTION 4 btequalimage(oid) ; CREATE OPERATOR CLASS int4_ops DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS -- standard int4 comparisons OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint4cmp(int4, int4) , FUNCTION 2 btint4sortsupport(internal) , FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) , FUNCTION 4 btequalimage(oid) ; CREATE OPERATOR CLASS int2_ops DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS -- standard int2 comparisons OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint2cmp(int2, int2) , FUNCTION 2 btint2sortsupport(internal) , FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) , FUNCTION 4 btequalimage(oid) ; ALTER OPERATOR FAMILY integer_ops USING btree ADD -- cross-type comparisons int8 vs int2 OPERATOR 1 < (int8, int2) , OPERATOR 2 <= (int8, int2) , OPERATOR 3 = (int8, int2) , OPERATOR 4 >= (int8, int2) , OPERATOR 5 > (int8, int2) , FUNCTION 1 btint82cmp(int8, int2) , -- cross-type comparisons int8 vs int4 OPERATOR 1 < (int8, int4) , OPERATOR 2 <= (int8, int4) , OPERATOR 3 = (int8, int4) , OPERATOR 4 >= (int8, int4) , OPERATOR 5 > (int8, int4) , FUNCTION 1 btint84cmp(int8, int4) , -- cross-type comparisons int4 vs int2 OPERATOR 1 < (int4, int2) , OPERATOR 2 <= (int4, int2) , OPERATOR 3 = (int4, int2) , OPERATOR 4 >= (int4, int2) , OPERATOR 5 > (int4, int2) , FUNCTION 1 btint42cmp(int4, int2) , -- cross-type comparisons int4 vs int8 OPERATOR 1 < (int4, int8) , OPERATOR 2 <= (int4, int8) , OPERATOR 3 = (int4, int8) , OPERATOR 4 >= (int4, int8) , OPERATOR 5 > (int4, int8) , FUNCTION 1 btint48cmp(int4, int8) , -- cross-type comparisons int2 vs int8 OPERATOR 1 < (int2, int8) , OPERATOR 2 <= (int2, int8) , OPERATOR 3 = (int2, int8) , OPERATOR 4 >= (int2, int8) , OPERATOR 5 > (int2, int8) , FUNCTION 1 btint28cmp(int2, int8) , -- cross-type comparisons int2 vs int4 OPERATOR 1 < (int2, int4) , OPERATOR 2 <= (int2, int4) , OPERATOR 3 = (int2, int4) , OPERATOR 4 >= (int2, int4) , OPERATOR 5 > (int2, int4) , FUNCTION 1 btint24cmp(int2, int4) , -- cross-type in_range functions FUNCTION 3 in_range(int4, int4, int8, boolean, boolean) , FUNCTION 3 in_range(int4, int4, int2, boolean, boolean) , FUNCTION 3 in_range(int2, int2, int8, boolean, boolean) , FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;
请注意,此定义“重载”了操作符策略和支持函数编号:每个编号在该族中出现多次。只要特定编号的每个实例都具有不同的输入数据类型,就允许这样做。两个输入类型都等于操作符类输入类型的实例是该操作符类的主要操作符和支持函数,在大多数情况下,应该声明为操作符类的一部分,而不是作为该族的松散成员。
在 B 树操作符族中,族中的所有操作符都必须以兼容的方式排序,这在第 64.1.2 节中有详细说明。对于族中的每个操作符,都必须有一个与该操作符具有相同两个输入数据类型的支持函数。建议一个族是完整的,即对于数据类型的每种组合,都包含所有操作符。每个操作符类应仅包含其数据类型的非跨类型操作符和支持函数。
要构建多数据类型哈希操作符族,必须为该族支持的每种数据类型创建兼容的哈希支持函数。这里的兼容性意味着保证对于被该族的相等操作符认为是相等的任何两个值,即使值是不同的类型,这些函数也会返回相同的哈希码。当类型具有不同的物理表示时,通常很难实现这一点,但在某些情况下是可以实现的。此外,通过隐式或二进制强制转换,将操作符族中表示的一种数据类型的值转换为也在操作符族中表示的另一种数据类型,也不得更改计算出的哈希值。请注意,每种数据类型只有一个支持函数,而不是每个相等操作符一个。建议一个族是完整的,即为数据类型的每种组合提供一个相等操作符。每个操作符类应仅包含其数据类型的非跨类型相等操作符和支持函数。
GiST、SP-GiST 和 GIN 索引没有任何明确的跨数据类型操作概念。支持的操作符集只是给定操作符类的主支持函数可以处理的任何内容。
在 BRIN 中,需求取决于提供操作符类的框架。对于基于 minmax
的操作符类,所需的行为与 B 树操作符族相同:族中的所有操作符都必须以兼容的方式排序,并且强制转换不得更改相关的排序顺序。
在 PostgreSQL 8.3 之前,没有操作符族的概念,因此任何旨在与索引一起使用的跨数据类型操作符都必须直接绑定到索引的操作符类中。虽然这种方法仍然有效,但它已被弃用,因为它使索引的依赖关系过于宽泛,并且当两种数据类型在同一操作符族中都有操作符时,规划器可以更有效地处理跨数据类型的比较。
PostgreSQL 使用操作符类来推断操作符的属性,其方式不仅仅是它们是否可以与索引一起使用。因此,即使您不打算为数据类型的任何列建立索引,您也可能想要创建操作符类。
特别是,存在诸如 ORDER BY
和 DISTINCT
等 SQL 功能,它们需要比较和排序值。要在用户定义的数据类型上实现这些功能,PostgreSQL 会查找该数据类型的默认 B 树操作符类。该操作符类的“等于”成员定义了系统对 GROUP BY
和 DISTINCT
的值相等概念,以及操作符类施加的排序顺序定义了默认的 ORDER BY
排序。
如果数据类型没有默认的 B 树操作符类,系统将查找默认的哈希操作符类。但是,由于这种操作符类仅提供相等性,因此它只能支持分组,而不能支持排序。
当数据类型没有默认的操作符类时,如果您尝试将这些 SQL 功能与该数据类型一起使用,您将收到类似 “无法识别排序操作符”的错误。
在 7.4 之前的 PostgreSQL 版本中,排序和分组操作将隐式使用名为 =
、<
和 >
的操作符。依赖默认操作符类的新行为避免了对具有特定名称的操作符的行为进行任何假设。
可以通过在 USING
选项中指定该类的 小于操作符,来按非默认的 B 树操作符类进行排序,例如
SELECT * FROM mytable ORDER BY somecol USING ~<~;
或者,在 USING
中指定该类的大于操作符,选择降序排序。
用户定义类型的数组的比较也依赖于该类型的默认 B 树操作符类定义的语义。如果没有默认的 B 树操作符类,但有默认的哈希操作符类,则支持数组相等性,但不支持排序比较。
另一个需要更多数据类型特定知识的 SQL 功能是窗口函数的 RANGE
offset
PRECEDING
/FOLLOWING
框架选项(请参阅第 4.2.8 节)。对于诸如以下的查询
SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING) FROM mytable;
仅知道如何按 x
排序是不够的;数据库还必须了解如何将“减 5”或“加 10”到当前行的 x
值,以识别当前窗口框架的边界。使用定义 ORDER BY
排序的 B 树操作符类提供的比较操作符,可以比较得到的边界与 x
的其他行值 - 但加法和减法操作符不是操作符类的一部分,那么应该使用哪些呢?硬编码该选择是不可取的,因为不同的排序顺序(不同的 B 树操作符类)可能需要不同的行为。因此,B 树操作符类可以指定一个 in_range 支持函数,该函数封装了对于其排序顺序有意义的加法和减法行为。如果存在多种数据类型作为 RANGE
子句中偏移量使用是合理的,它甚至可以提供多个 in_range 支持函数。如果与窗口的 ORDER BY
子句关联的 B 树操作符类没有匹配的 in_range 支持函数,则不支持 RANGE
offset
PRECEDING
/FOLLOWING
选项。
另一个重要点是,出现在哈希操作符族中的相等操作符是哈希连接、哈希聚合和相关优化的候选项。哈希操作符族在这里至关重要,因为它标识了要使用的哈希函数。
一些索引访问方法(目前只有 GiST 和 SP-GiST)支持排序操作符的概念。到目前为止,我们一直在讨论的是搜索操作符。搜索操作符是一种可以搜索索引以查找满足 WHERE
indexed_column
operator
constant
的所有行的操作符。请注意,不保证返回匹配行的顺序。相反,排序操作符不限制可以返回的行集,而是确定它们的顺序。排序操作符是一种可以扫描索引以按 ORDER BY
indexed_column
operator
constant
表示的顺序返回行的操作符。以这种方式定义排序操作符的原因是,如果该操作符是测量距离的操作符,则它支持最近邻搜索。例如,像这样的查询
SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
查找最接近给定目标点的十个地点。位置列上的 GiST 索引可以有效地执行此操作,因为 <->
是排序操作符。
虽然搜索操作符必须返回布尔结果,但排序操作符通常返回其他类型,例如用于距离的 float 或 numeric。此类型通常与索引的数据类型不同。为了避免对不同数据类型的行为进行硬编码假设,排序操作符的定义需要命名一个 B 树操作符族,该族指定结果数据类型的排序顺序。如上一节所述,B 树操作符族定义了 PostgreSQL 的排序概念,因此这是一种自然的表示形式。由于点 <->
操作符返回 float8
,因此可以在如下的操作符类创建命令中指定它
OPERATOR 15 <-> (point, point) FOR ORDER BY float_ops
其中 float_ops
是内置的操作符族,包含对 float8
的操作。这个声明表明索引能够按照 <->
操作符的值递增的顺序返回行。
操作符类有两个我们尚未讨论的特殊特性,主要是因为它们在最常用的索引方法中并不常用。
通常,声明一个操作符为一个操作符类(或族)的成员意味着索引方法可以准确地检索满足使用该操作符的 WHERE
条件的行集。例如
SELECT * FROM table WHERE integer_column < 4;
可以通过整数列上的 B 树索引精确满足。但是,在某些情况下,索引可以作为匹配行的不精确指南。例如,如果 GiST 索引仅存储几何对象的边界框,则它不能完全满足测试非矩形对象(如多边形)之间重叠的 WHERE
条件。但是,我们可以使用索引查找边界框与目标对象的边界框重叠的对象,然后仅对索引找到的对象执行精确的重叠测试。如果这种情况适用,则该索引对于该操作符来说是“有损的”。有损索引搜索的实现方式是:当行可能满足也可能不满足查询条件时,索引方法返回一个重新检查标志。核心系统随后将测试检索到的行上的原始查询条件,以查看是否应将其作为有效匹配项返回。如果索引保证返回所有必需的行,以及可能的一些额外行,这些额外行可以通过执行原始操作符调用来消除,则此方法有效。支持有损搜索的索引方法(当前为 GiST、SP-GiST 和 GIN)允许各个操作符类的支持函数设置重新检查标志,因此这本质上是操作符类的一个特性。
再次考虑这样一种情况,即我们仅在索引中存储诸如多边形之类的复杂对象的边界框。在这种情况下,在索引条目中存储整个多边形没有太多价值 — 我们不妨仅存储一个类型为 box
的更简单的对象。这种情况通过 CREATE OPERATOR CLASS
中的 STORAGE
选项表示:我们可以这样写
CREATE OPERATOR CLASS polygon_ops DEFAULT FOR TYPE polygon USING gist AS ... STORAGE box;
目前,只有 GiST、SP-GiST、GIN 和 BRIN 索引方法支持与列数据类型不同的 STORAGE
类型。当使用 STORAGE
时,GiST compress
和 decompress
支持例程必须处理数据类型转换。同样,当存储类型不同时,SP-GiST 也需要 compress
支持函数来转换为存储类型;如果 SP-GiST 操作符类也支持检索数据,则反向转换必须由 consistent
函数处理。在 GIN 中,STORAGE
类型标识 “键”值的类型,该类型通常与索引列的类型不同 — 例如,整数数组列的操作符类可能具有仅为整数的键。GIN extractValue
和 extractQuery
支持例程负责从索引值中提取键。BRIN 与 GIN 类似:STORAGE
类型标识存储的摘要值的类型,并且操作符类的支持过程负责正确解释摘要值。
如果您在文档中发现任何不正确的内容、与您使用特定功能的经验不符或需要进一步澄清的内容,请使用此表单报告文档问题。