到目前为止所描述的过程使我们能够定义新类型、新函数以及新操作符。然而,我们还不能在一种新数据类型的列上定义索引。为此,必须为该新数据类型定义一个操作符类。本节稍后将用一个示例说明这一概念:为 B-树索引方法定义一个新的操作符类,以便按绝对值升序存储和排序复数。
操作符类可以分组成操作符族,以展示语义兼容的类之间的关系。只涉及单一数据类型时,一个操作符类就已足够,因此我们先关注这种情况,然后再回到操作符族。
pg_am 表为每个索引方法(内部称为访问方法)保存一行。对表进行常规访问的支持内置于 PostgreSQL 中,但所有索引方法都在 pg_am 中描述。可以编写必要的代码,然后在 pg_am 中创建一行,从而添加新的索引访问方法 — 但这超出了本章的范围(参见Chapter 60)。
索引方法的例程并不直接知道它将处理哪些数据类型。相反,一个操作符类标识了索引方法在处理特定数据类型时需要使用的那组操作。之所以称为操作符类,是因为它指定的一项内容就是可与索引一起使用的 WHERE 子句操作符集合(也就是能被转换成索引扫描条件的操作符)。操作符类还可以指定索引方法内部操作所需的一些支持函数,但这些函数并不直接对应任何可与索引一起使用的 WHERE 子句操作符。
可以为同一种数据类型和索引方法定义多个操作符类。这样就能为一种数据类型定义多套索引语义。例如,一个 B-树索引要求为其处理的每一种数据类型定义一种排序顺序。对于复数数据类型,也许既需要一个按复数绝对值排序的 B-树操作符类,也需要另一个按实部排序的操作符类,等等。通常,其中一个操作符类会被视为最常用,并标记为该数据类型在该索引方法上的默认操作符类。
同一个操作符类名可以用于多个不同的索引方法(例如,B-树和哈希索引方法都有名为 int4_ops 的操作符类),但每一个这样的类都是独立实体,必须分别定义。
与操作符类关联的操作符通过“策略号”来标识,用以表示每个操作符在其操作符类上下文中的语义。例如,B-树对键施加了严格的从小到大的顺序,因此像“小于”和“大于等于”这样的操作符,对 B-树来说就很重要。由于 PostgreSQL 允许用户定义操作符,PostgreSQL 不能仅凭操作符名称(例如 < 或 >=)就判断它是哪一类比较。取而代之的是,索引方法定义了一组“策略”,可以把它们看成是广义的操作符。每个操作符类都会说明,对于某种特定数据类型和某种索引语义解释,每一种策略分别对应哪个实际操作符。
B-树索引方法定义了五种策略,如Table 37.2所示。
Table 37.2. B-树策略
| 操作 | 策略号 |
|---|---|
| 小于 | 1 |
| 小于等于 | 2 |
| 等于 | 3 |
| 大于等于 | 4 |
| 大于 | 5 |
哈希索引只支持等值比较,因此它们只使用一种策略,如Table 37.3所示。
Table 37.3. 哈希策略
| 操作 | 策略号 |
|---|---|
| 等于 | 1 |
GiST 索引更加灵活:它们根本没有固定的策略集合。相反,每个特定 GiST 操作符类中负责“一致性”检查的支持例程会按自己的方式解释策略号。举例来说,一些内置的 GiST 索引操作符类会为二维几何对象建立索引,并提供“R 树”策略,如Table 37.4所示。其中四个是真正的二维测试(重叠、相同、包含、被包含),四个只考虑 X 方向,另外四个则在 Y 方向上提供相同测试。
Table 37.4. GiST 二维“R 树”策略
| 操作 | 策略号 |
|---|---|
| 严格位于左侧 | 1 |
| 不延伸到右侧 | 2 |
| 重叠 | 3 |
| 不延伸到左侧 | 4 |
| 严格位于右侧 | 5 |
| 相同 | 6 |
| 包含 | 7 |
| 被包含 | 8 |
| 不延伸到上方 | 9 |
| 严格位于下方 | 10 |
| 严格位于上方 | 11 |
| 不延伸到下方 | 12 |
SP-GiST 索引在灵活性方面与 GiST 索引类似:它们也没有固定的策略集合。相反,每个操作符类的支持例程会根据该操作符类的定义解释策略号。举例来说,内置点操作符类所使用的策略号如Table 37.5所示。
Table 37.5. SP-GiST 点策略
| 操作 | 策略号 |
|---|---|
| 严格位于左侧 | 1 |
| 严格位于右侧 | 5 |
| 相同 | 6 |
| 被包含 | 8 |
| 严格位于下方 | 10 |
| 严格位于上方 | 11 |
GIN 索引与 GiST 和 SP-GiST 索引类似,也没有固定的策略集合。相反,每个操作符类的支持例程会根据该操作符类的定义解释策略号。举例来说,内置数组操作符类所使用的策略号如Table 37.6所示。
Table 37.6. GIN 数组策略
| 操作 | 策略号 |
|---|---|
| 重叠 | 1 |
| 包含 | 2 |
| 被包含 | 3 |
| 等于 | 4 |
BRIN 索引也与 GiST、SP-GiST 和 GIN 索引一样,没有固定的策略集合。每个操作符类的支持函数都会根据该操作符类的定义解释策略号。举例来说,内置 Minmax 操作符类所使用的策略号如Table 37.7所示。
Table 37.7. BRIN Minmax 策略
| 操作 | 策略号 |
|---|---|
| 小于 | 1 |
| 小于等于 | 2 |
| 等于 | 3 |
| 大于等于 | 4 |
| 大于 | 5 |
注意,上面列出的所有操作符都返回布尔值。实际上,所有被定义为索引方法搜索操作符的操作符都必须返回 boolean,因为要与索引配合使用,它们必须出现在 WHERE 子句的顶层。(某些索引访问方法还支持排序操作符,这类操作符通常不返回布尔值;该特性见Section 37.14.7。)
仅靠策略信息通常不足以让系统知道如何使用索引。实际上,索引方法还需要额外的支持例程才能工作。例如,B-树索引方法必须能够比较两个键,并判断其中一个是大于、等于还是小于另一个。类似地,哈希索引方法必须能够为键值计算哈希码。这些操作并不对应 SQL 命令条件中使用的操作符;它们是索引方法内部使用的管理例程。
与策略一样,操作符类会标识对于给定的数据类型和语义解释,应由哪些具体函数承担这些角色。索引方法定义它需要的函数集合,而操作符类则会通过为函数分配由索引方法规定的“支持函数号”来标识正确的函数。
如Table 37.8所示,B-树要求一个支持函数,并允许操作符类作者按需再提供一个支持函数。
Table 37.8. B-树支持函数
| 函数 | 支持号 |
|---|---|
| 比较两个键,并返回小于零、等于零或大于零的整数,分别表示第一个键小于、等于或大于第二个键 | 1 |
返回可从 C 调用的排序支持函数的地址,详见 utils/sortsupport.h(可选) |
2 |
哈希索引要求一个支持函数,如Table 37.9所示。
Table 37.9. 哈希支持函数
| 函数 | 支持号 |
|---|---|
| 计算一个键的哈希值 | 1 |
GiST 索引有九个支持函数,其中两个是可选的,如Table 37.10所示。 (详见Chapter 62。)
Table 37.10. GiST 支持函数
| 函数 | 描述 | 支持号 |
|---|---|---|
consistent |
确定键是否满足查询条件 | 1 |
union |
计算一组键的并集 | 2 |
compress |
计算将被索引的键或值的压缩表示 | 3 |
decompress |
计算压缩键的解压表示 | 4 |
penalty |
计算把新键插入具有给定子树键的子树时的罚值 | 5 |
picksplit |
确定页面中的哪些项要移到新页面,并计算结果页面的并集键 | 6 |
equal |
比较两个键,并在它们相等时返回真 | 7 |
distance |
确定键到查询值的距离(可选) | 8 |
fetch |
为仅索引扫描计算压缩键的原始表示(可选) | 9 |
如Table 37.11所示,SP-GiST 索引要求五个支持函数。(详见Chapter 63。)
Table 37.11. SP-GiST 支持函数
| 函数 | 描述 | 支持号 |
|---|---|---|
config |
提供有关该操作符类的基本信息 | 1 |
choose |
确定如何把一个新值插入内部元组 | 2 |
picksplit |
确定如何划分一组值 | 3 |
inner_consistent |
确定对某个查询需要搜索哪些子分区 | 4 |
leaf_consistent |
确定键是否满足查询条件 | 5 |
如Table 37.12所示,GIN 索引有六个支持函数,其中三个是可选的(详见Chapter 64)。
Table 37.12. GIN 支持函数
| 函数 | 描述 | 支持号 |
|---|---|---|
compare |
比较两个键,并返回一个小于零、等于零或大于零的整数,以表示第一个键是小于、等于还是大于第二个键 | 1 |
extractValue |
从待索引值中提取键 | 2 |
extractQuery |
从查询条件中提取键 | 3 |
consistent |
确定值是否匹配查询条件(布尔变体;如果支持函数 6 存在则可选) | 4 |
comparePartial |
比较查询中的部分键与索引中的键,并返回一个小于零、等于零或大于零的整数,用以指示 GIN 应忽略该索引项、将该项视为匹配,还是停止索引扫描(可选) | 5 |
triConsistent |
确定值是否匹配查询条件(三值变体;如果支持函数 4 存在则可选) | 6 |
BRIN 索引有四个基本支持函数,如Table 37.13所示;这些基本函数可能要求提供额外的支持函数。(详见Section 65.3。)
Table 37.13. BRIN 支持函数
| 函数 | 描述 | 支持号 |
|---|---|---|
opcInfo |
返回描述被索引列摘要数据的内部信息 | 1 |
add_value |
向一个现有的摘要索引元组增加一个新值 | 2 |
consistent |
确定值是否匹配查询条件 | 3 |
union |
计算两个摘要元组的并集 | 4 |
与搜索操作符不同,支持函数返回的是特定索引方法所期望的数据类型;例如,对 B-树的比较函数来说,就是一个有符号整数。每个支持函数的参数个数和类型也同样取决于索引方法。对于 B-树和哈希,比较支持函数和哈希支持函数接受的输入数据类型,与操作符类中包含的操作符相同;但对大多数 GiST、SP-GiST、GIN 和 BRIN 支持函数来说并非如此。
现在我们已经了解了这些基本思想,下面给出先前承诺的创建新操作符类示例。(这个可运行示例位于源码发布包中的src/tutorial/complex.c和src/tutorial/complex.sql。)该操作符类封装了一组按绝对值顺序对复数排序的操作符,因此我们把它命名为complex_abs_ops。首先,我们需要一组操作符。定义操作符的过程已经在Section 37.12中讨论过。对于 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);
}
其他四个函数的区别只在于它们如何比较内部函数的结果与 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) ; 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) ; 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) ; 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) ;
注意,此定义“重载”了操作符策略编号和支持函数编号:每个编号在操作符族中出现多次。只要同一编号的每个实例具有不同的输入数据类型,这就是允许的。两个输入类型都等于某个操作符类输入类型的实例,是该操作符类的主要操作符和支持函数,通常应声明为操作符类的一部分,而不是操作符族的独立成员。
在一个 B-树操作符族中,该族中的所有操作符都必须以兼容的方式排序,也就是说,传递律必须适用于该族支持的所有数据类型:“如果 A = B 且 B = C,则 A = C”,以及“如果 A < B 且 B < C,则 A < C”。此外,操作符族中所表示的类型之间的隐式或二进制强制类型转换不得改变相关的排序顺序。对族中的每一个操作符,都必须有一个具有相同两个输入数据类型的支持函数。建议让操作符族保持完整,也就是说,对每一种数据类型组合都应包含全部操作符。每个操作符类只应包含其数据类型对应的非跨数据类型操作符和支持函数。
要构建一个多数据类型的哈希操作符族,必须为该族支持的每一种数据类型创建相互兼容的哈希支持函数。这里的兼容性是指:对任意两个被该族中的等值操作符视为相等的值,这些函数都保证返回相同的哈希码,即使这两个值属于不同类型也是如此。当这些类型具有不同的物理表示时,这通常难以实现,但在某些情况下可以做到。此外,将该操作符族中一种数据类型的值通过隐式或二进制强制转换转换成该族中另一种数据类型时,不应改变所计算出的哈希值。注意,每种数据类型只有一个支持函数,而不是每个等值操作符一个。建议让操作符族保持完整,也就是说,对每一种数据类型组合都提供一个等值操作符。每个操作符类只应包含其数据类型对应的非跨数据类型等值操作符和支持函数。
GiST、SP-GiST 和 GIN 索引没有任何显式的跨数据类型操作概念。它们所支持的操作符集合,就是给定操作符类的主要支持函数所能处理的那些操作符。
在 BRIN 中,要求取决于提供操作符类的框架。对于基于 minmax 的操作符类,所要求的行为与 B-树操作符族相同:族中的所有操作符都必须以兼容的方式排序,并且类型转换不能改变相关的排序顺序。
在 PostgreSQL 8.3 之前,并没有操作符族这一概念,因此任何打算与索引一起使用的跨数据类型操作符都必须直接绑定到该索引的操作符类中。虽然这种做法仍然有效,但已经废弃,因为它会使索引的依赖关系过于宽泛,而且当两种数据类型都在同一操作符族中拥有操作符时,规划器能更有效地处理跨数据类型比较。
PostgreSQL利用操作符类来从多方面推断操作符的属性,而不仅仅是判断它们能否用于索引。因此,即便你并不打算为自己的数据类型列建立索引,也可能会想创建操作符类。
特别地,ORDER BY和DISTINCT等 SQL 特性要求对值的比较和排序。为了在用户定义的数据类型上实现这些特性,PostgreSQL会为数据类型查找默认 B-树操作符类。这个操作符类的“equals”成员定义了用于GROUP BY和DISTINCT的值的等值概念,而该操作符类施加的排序顺序定义了默认的ORDER BY顺序。
用户定义类型的数组比较也依赖于该类型默认 B-树操作符类所定义的语义。
如果一种数据类型没有默认的 B-树操作符类,系统就会查找默认的哈希操作符类。但由于这类操作符类只提供等值语义,因此在实践中它只足以支持数组相等比较。
如果某种数据类型没有默认操作符类,而你又试图将这些 SQL 特性用于该数据类型,就会得到类似“无法识别排序操作符”这样的错误。
在版本 7.4 以前的PostgreSQL中,排序和分组操作将隐式地使用名为=、<以及>的操作符。新的依赖于默认操作符类的行为避免了对具有特定名字的操作符行为作出任何假设。
另一个重要点在于,出现在哈希操作符族中的操作符,都是哈希连接、哈希聚合以及相关优化的候选对象。这里哈希操作符族至关重要,因为它标识了应当使用的哈希函数。
有些索引访问方法(目前只有 GiST)支持排序操作符的概念。我们到目前为止讨论的是搜索操作符。对于搜索操作符,可以搜索索引以找出满足以下条件的所有行:WHERE indexed_column operator constant。注意,不保证匹配行的返回顺序。排序操作符则不限制可以返回的行集合,而是确定这些行的顺序。对于排序操作符,可以扫描索引,按以下表达式表示的顺序返回行:ORDER BY indexed_column operator constant。这样定义排序操作符,是因为当操作符用于度量距离时,它可以支持最近邻搜索。例如,如下查询:
SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
可以找到距离指定目标点最近的十个地点。location 列上的 GiST 索引能够高效完成此操作,因为<->是排序操作符。
搜索操作符必须返回布尔结果,而排序操作符通常返回其他类型的结果,例如表示距离的浮点数或数值。这种类型通常不同于被索引的数据类型。为了避免对不同数据类型行为作硬编码假设,在定义排序操作符时,必须指定一个 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 条件。不过,我们仍然可以利用该索引找出边界框与目标对象边界框重叠的对象,然后只对索引找到的那些对象执行精确的重叠测试。如果适用于这种场景,就称该索引对这个操作符是“有损的”。有损索引搜索的实现方式是:当某一行可能满足、也可能不满足查询条件时,由索引方法返回一个recheck标志。随后,核心系统会在取回的行上重新测试原始查询条件,以判断它是否应当作为合法匹配返回。只要索引能保证返回所有必需的行,外加可能存在的一些额外行,这种方法就是有效的,因为这些额外行可以通过执行原始操作符调用来剔除。支持有损搜索的索引方法(目前有 GiST、SP-GiST 和 GIN)允许个别操作符类的支持函数设置 recheck 标志,因此这本质上也是一种操作符类特性。
再次考虑只在索引中存储多边形等复杂对象的包围盒的情况。此时,在索引条目中存储整个多边形没有多少价值,不如只存储一个更简单的对象,其类型为box。这种情况由STORAGE选项表达,该选项位于CREATE OPERATOR CLASS:可以写成如下形式:
CREATE OPERATOR CLASS polygon_ops
DEFAULT FOR TYPE polygon USING gist AS
...
STORAGE box;
目前,只有 GiST、GIN 和 BRIN 索引方法支持与列数据类型不同的STORAGE类型。GiST 的compress和decompress支持函数在使用STORAGE时必须处理数据类型转换。在 GIN 中,STORAGE类型标识“key”值的类型,通常与被索引列的类型不同。例如,整数数组列的操作符类可以只使用整数作为键。GIN 的extractValue和extractQuery支持函数负责从被索引值中提取键。BRIN 与 GIN 类似:STORAGE类型标识所存储摘要值的类型,而操作符类的支持函数负责正确解释这些摘要值。