Postgresql 中文操作指南

38.16. Interfacing Extensions to Indexes #

到目前为止描述的过程可以定义新类型、新函数和新运算符。但是,我们还不能在新型数据类型的列上定义索引。要做到这一点,我们必须为该新型数据类型定义 operator class。在本节后面,我们将举例说明此概念:B 树索引方法的新运算符类,该类按升序绝对值顺序存储和排序复数。

运算符类可以分组到 operator families 中以显示语义兼容类之间的关系。当只涉及单个数据类型时,一个运算符类就足够了,所以我们先把重点放在这种情况上,然后返回到运算符族。

38.16.1. Index Methods and Operator Classes #

_pg_am_表包含每个索引方法(在内部称为访问方法)的一行。对表进行常规访问的支持已内置于 PostgreSQL 中,但所有索引方法都在 _pg_am_中进行了描述。可以通过编写必要的代码然后在 _pg_am_中创建条目来添加新的索引访问方法 — 但这超出了本章的范围(请参见 Chapter 64)。

索引方法的例程不会直接知道索引方法将操作的数据类型。相反,operator class 标识索引方法使用特定数据类型所需的一组操作。运算符类之所以这样命名,是因为它们指定的一件事是可以与索引一起使用的 WHERE 子句运算符(即,可以转换为索引扫描限定条件)。运算符类还可以指定索引方法的内部操作所需的 support function,但不要直接对应于可以与索引一起使用的任何 WHERE 子句运算符。

可以为相同的数据类型和索引方法定义多个运算符类。通过这样做,可以为单个数据类型定义多组索引语义。例如,B 树索引需要为其处理的每种数据类型定义一个排序顺序。让复数数据类型拥有一个按复绝对值排序的 B 树运算符类,另一个按实部排序的运算符类等等,可能会很有用。通常,其中一个运算符类将被认为最常用,并将标记为该数据类型和索引方法的默认运算符类。

相同的运算符类名称可用于多个不同的索引方法(例如,B 树和哈希索引方法都有名为 int4_ops 的运算符类),但每个此类都是一个独立的实体,并且必须单独定义。

38.16.2. Index Method Strategies #

与运算符类关联的运算符由“策略编号”标识,该编号用于在运算符类的上下文中标识每个运算符的语义。例如,B 树对键强制执行严格的从较小到较大的排序顺序,因此“小于”和“大于或等于”之类的运算符对于 B 树来说很有趣。由于 PostgreSQL 允许用户定义运算符,因此 PostgreSQL 无法查看运算符的名称(例如 <>=)并说出它是什么类型的比较。相反,索引方法定义了一组“策略”,可以将其视为通用运算符。每个运算符类指定哪种实际运算符对应于特定数据类型的每个策略和索引语义解释。

B-tree 索引方法定义了五个策略,如 Table 38.3所示。

Table 38.3. B-Tree Strategies

Operation

Strategy Number

less than

1

less than or equal

2

equal

3

greater than or equal

4

greater than

5

哈希索引仅支持相等性比较,因此仅使用一种策略,如 Table 38.4所示。

Table 38.4. Hash Strategies

Operation

Strategy Number

equal

1

GiST 索引更为灵活:它们根本没有一组固定的策略。相反,每个特定 GiST 运算符类的“一致性”支持例程根据自己的喜好解释策略编号。作为一个示例,几个内置 GiST 索引运算符类索引二维几何对象,提供了 Table 38.5中所示的“R-tree”策略。其中四个是真正的二维测试(重叠、相同、包含、被包含);其中四个仅考虑 X 方向;其他四个在 Y 方向提供相同的测试。

Table 38.5. GiST Two-Dimensional “R-tree” Strategies

Operation

Strategy Number

strictly left of

1

不扩展向右侧

2

overlaps

3

不扩展向左侧

4

strictly right of

5

same

6

contains

7

contained by

8

does not extend above

9

strictly below

10

strictly above

11

does not extend below

12

SP-GiST 索引在灵活性上类似于 GiST 索引:它们没有一组固定的策略。相反,每个运算符类的支持例程根据运算符类的定义解释策略编号。作为一个示例,内置运算符类为点使用的策略编号如 Table 38.6所示。

Table 38.6. SP-GiST Point Strategies

Operation

Strategy Number

strictly left of

1

strictly right of

5

same

6

contained by

8

strictly below

10

strictly above

11

GIN 索引类似于 GiST 和 SP-GiST 索引,因为它们也没有一组固定的策略。相反,每个运算符类的支持例程根据运算符类的定义解释策略编号。作为一个示例,内置运算符类为数组使用的策略编号如 Table 38.7所示。

Table 38.7. GIN Array Strategies

Operation

Strategy Number

overlap

1

contains

2

is contained by

3

equal

4

BRIN 索引类似于 GiST、SP-GiST 和 GIN 索引,因为它们也没有一组固定的策略。相反,每个运算符类的支持例程根据运算符类的定义解释策略编号。作为一个示例,内置 _Minmax_运算符类使用的策略编号如 Table 38.8所示。

Table 38.8. BRIN Minmax Strategies

Operation

Strategy Number

less than

1

less than or equal

2

equal

3

greater than or equal

4

greater than

5

请注意,上面列出的所有运算符都返回布尔值。在实践中,所有定义为索引方法搜索运算符的运算符必须返回 boolean_类型,因为它们必须出现在 _WHERE_子句的顶层才能与索引一起使用。(某些索引访问方法还支持 _ordering operators,通常不返回布尔值;该功能在 Section 38.16.7中进行了讨论。)

38.16.3. Index Method Support Routines #

策略通常对于系统来说并不足以了解如何使用索引。实际上,索引方法需要额外的支持例程才能工作。例如,B 树索引方法必须能够比较两个键并确定一个键是否大于、等于或小于另一个键。类似地,哈希索引方法必须能够计算键值的哈希码。这些操作与 SQL 命令中的限定条件中使用的运算符不对应;它们是索引方法在内部使用的管理例程。

与策略一样,运算符类标识哪些特定函数应针对给定的数据类型和语义解释来扮演这些角色。索引方法定义它需要的一组函数,而运算符类通过将它们分配给索引方法指定的“支持函数号”来标识要使用的正确函数。

此外,某些 opclass 允许用户指定控制其行为的参数。每个内置索引访问方法都有一个可选的 options 支持函数,该函数定义一组特定于 opclass 的参数。

B-树需要一个比较支持函数,并且允许以操作符类作者的选择在 Table 38.9 中所示提供四个额外的支持函数。这些支持函数的需求在 Section 67.3 中有进一步的解释。

Table 38.9. B-Tree Support Functions

Function

Support Number

比较两个键并返回整数,小于零、等于零或大于零,表示第一个键是否小于、等于或大于第二个键

1

返回可调用 C 的排序支持函数的地址(可选)

2

将测试值与基值加/减偏移量进行比较,并根据比较结果返回 true 或 false(可选)

3

确定使用操作员类的索引是否可以安全地应用 B 树重复数据删除优化(可选)

4

定义特定于此操作员类(可选)的选项

5

哈希索引需要一个支持函数,并且允许以操作符类作者的选择在 Table 38.10 中所示提供两个。

Table 38.10. Hash Support Functions

Function

Support Number

计算键的 32 位哈希值

1

针对 64 位随机数计算给定键的 64 位哈希值;如果随机数为 0,则结果的低 32 位必须与函数 1 计算的值匹配(可选)

2

定义特定于此操作员类(可选)的选项

3

GiST 索引有 11 个支持函数,其中 6 个是可选的,如图 Table 38.11 所示。(更多信息请见 Chapter 68。)

Table 38.11. GiST Support Functions

Function

Description

Support Number

consistent

确定键是否满足查询限定符

1

union

计算一组键的并集

2

compress

计算要索引的键或值的压缩表示(可选)

3

decompress

计算压缩键的解压缩表示(可选)

4

penalty

计算将新键插入具有给定子树键的子树的惩罚

5

picksplit

确定页面的哪些条目应移到新页面,并计算由此页面产生的并集键

6

same

比较两个键并返回 true(如果它们相等)

7

distance

确定 key 到 query 值的距离(可选)

8

fetch

计算仅限索引扫描的压缩 key 的原始表示(可选)

9

options

定义特定于该运算符类的选项(可选)

10

sortsupport

提供要在快速索引构建中使用的排序比较器(可选)

11

SP-GiST 索引有 6 个支持函数,其中 1 个是可选的,如图 Table 38.12 所示。(更多信息请见 Chapter 69。)

Table 38.12. SP-GiST Support Functions

Function

Description

Support Number

config

提供有关运算符类的基本信息

1

choose

确定如何将新值插入到内部元组中

2

picksplit

确定如何对一组值进行分区

3

inner_consistent

确定需要为查询搜索哪些子分区

4

leaf_consistent

确定键是否满足查询限定符

5

options

定义特定于该运算符类的选项(可选)

6

GIN 索引有 7 个支持函数,其中 4 个是可选的,如图 Table 38.13 所示。(更多信息请见 Chapter 70。)

Table 38.13. GIN Support Functions

Function

Description

Support Number

compare

比较两个 key 并返回小于零、零或大于零的整数,指示第一个 key 是否小于、等于或大于第二个 key

1

extractValue

从要编制索引的值中提取 key

2

extractQuery

从查询条件中提取 key

3

consistent

确定值是否与查询条件匹配(布尔变量)(如果出现支持函数 6,可选)

4

comparePartial

比较查询中的部分 key 和索引中的 key,并返回小于零、零或大于零的整数,指示 GIN 是否应忽略此索引条目、将条目视为匹配或停止索引扫描(可选)

5

triConsistent

确定值是否与查询条件匹配(三元变量)(如果出现支持函数 4,可选)

6

options

定义特定于该运算符类的选项(可选)

7

BRIN 索引有 5 个基本支持函数,其中 1 个是可选的,如图 Table 38.14 所示。某些版本的基本函数需要提供附加的支持函数。(更多信息请见 Section 71.3。)

Table 38.14. BRIN Support Functions

Function

Description

Support Number

opcInfo

返回描述已编制索引的列的摘要数据的内部信息

1

add_value

将新值添加到现有的摘要索引元组

2

consistent

确定值是否与查询条件匹配

3

union

计算两个摘要元组的并集

4

options

定义特定于该运算符类的选项(可选)

5

不同于搜索运算符,支持函数返回特定索引方法期望的任何数据类型;例如在 B 树的比较函数的情况下,为有符号整数。每个支持函数的参数数量和类型同样依赖于索引方法。对于 B 树和哈希,比较和哈希支持函数采用与运算符类中包含的运算符相同的数据类型作为输入,但对于大多数 GiST、SP-GiST、GIN 和 BRIN 支持函数,情况并非如此。

38.16.4. An Example #

既然我们了解了这些概念,这里有创建新的操作符类的示例。(此示例的可工作副本位于源代码分发版中的 src/tutorial/complex.csrc/tutorial/complex.sql。)操作符类封装按绝对值顺序对复数排序的操作符,因此我们选择名称 complex_abs_ops。首先,我们需要一组操作符。在 Section 38.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
);

指定正确的交换运算符和否定运算符以及合适的限制和联接选择性函数非常重要,否则优化器将无法有效利用索引。

这里还发生了其他值得注意的事情:

下一步是注册 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 即可。

38.16.5. Operator Classes and Operator Families #

到目前为止,我们隐式地假设一个运算符类只处理一种数据类型。虽然在一个特定索引列中当然只能有一种数据类型,但通常对将索引列与不同数据类型的值进行比较的操作进行索引很有用。此外,如果将跨数据类型运算符与运算符类结合使用,那么经常会遇到这样的情况:另一个数据类型有它自己的相关运算符类。显式地建立相关类之间的连接非常有用,因为这可以帮助规划器优化 SQL 查询(特别是对于 B 树运算符类而言,因为该规划器包含了大量有关如何使用它们方面的知识)。

为了满足这些需求,PostgreSQL 使用了一个名为 operator family 的概念。一个运算符族包含一个或多个运算符类,还可以包含可索引的运算符以及属于整个族的但不属于族中的任何单个类的相应支持函数。我们称这些运算符和函数在族中是“松散的”,而不是被绑定到特定类中。通常,每个运算符类包含单数据类型运算符,而跨数据类型运算符则在族中松散。

运算符族中的所有运算符和函数必须具有兼容的语义,其中兼容性要求由索引方法设定。因此,您可能想知道为什么还要将族的特定子集单独列为运算符类;事实上,对于许多目的而言,类划分是不相关的,而族是唯一的有趣分组。定义运算符类的目的是指定支持某个特定索引需要族的多少部分。如果存在一个使用运算符类的索引,那么在不删除索引的情况下不能删除该运算符类——但可以删除运算符族的其他部分,即其他运算符类和松散运算符。因此,应指定一个运算符类来包含与使用特定数据类型上的索引合理需要的运算符和函数的最小集合,然后可以将相关但非必要的运算符作为运算符族的松散成员添加进来。

作为一个示例,PostgreSQL 有一个内置的 B 树运算符族 integer_ops,它包括 int8_opsint4_opsint2_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-树操作符族中,该族中的所有操作符都必须在兼容的顺序中进行排序,如 Section 67.2 中详细指定的。对于该族中的每个操作符,必须存在一个支持函数,具有与该操作符相同的两种输入数据类型。建议族是完整的,即,对于每种数据类型的组合,都包括所有操作符。每个操作符类应仅包括其数据类型的非交叉类型操作符和支持函数。

要构建一个多数据类型哈希运算符族,必须为族支持的每种数据类型创建兼容的哈希支持函数。此处,兼容性是指这些函数保证对任何两个值返回相同的哈希代码,这些值被族的相等运算符视为相等,即使这些值是不同类型的也是如此。当类型具有不同的物理表示方式时,通常很难执行此操作,但在某些情况下可以执行。此外,通过隐式或二进制强制转换转换从运算符族中表示为一种数据类型到另一种数据类型的值,不得更改计算的哈希值。请注意,每个数据类型只有一个支持函数,而不是每个相等运算符一个。建议创建一个完整的族,即,为每个数据类型组合提供一个相等运算符。每个运算符类应仅包括非跨类型相等运算符和对数据类型的支持函数。

GiST、SP-GiST 和 GIN 索引没有任何显式的跨数据类型操作概念。所支持的运算符集只是给定运算符类的主要支持函数可以处理的任何内容。

在 BRIN 中,需求取决于提供运算符类的框架。对于基于 minmax 的运算符类,所需行为与 B 树运算符族的行为相同:族中的所有运算符都必须兼容排序,并且强制转换不能更改关联的排序顺序。

Note

在 PostgreSQL 8.3 之前,没有运算符系列的概念,所以任何打算与索引一起使用的跨数据类型运算符都必须直接绑定到索引的操作符类中。虽然此方法仍然可行,但它已被弃用,因为它使得索引的依赖性太广泛,并且当两个数据类型都具有同一操作符系列中的运算符时,计划程序可以更有效地处理跨数据类型的比较。

38.16.6. System Dependencies on Operator Classes #

除了是否可以将运算符与索引一起使用之外,PostgreSQL 还使用运算符类来推断更多方面的运算符特性。因此,即使您无意为任何数据类型列建立索引,您可能也想创建运算符类。

特别是,有 ORDER BYDISTINCT 等 SQL 特性,它们需要比较和排序值。为了在一个用户定义的数据类型中实现这些特性,PostgreSQL 将查找该数据类型的默认 B 树运算符类。此运算符类的“等于”成员定义了系统对 GROUP BYDISTINCT 值相等的理解,并且运算符类施加的排序顺序定义了默认 ORDER BY 顺序。

如果还没有数据类型的默认 B 树运算符类,系统将查找一个默认哈希运算符类。但由于该类型的运算符类仅提供相等性,因此它只能支持分组而不是排序。

如果还没有数据类型的默认运算符类,当您尝试将这些 SQL 特性与该数据类型一起使用时,您将收到“无法识别排序运算符”之类的错误。

Note

在 7.4 之前的 PostgreSQL 版本中,排序和分组操作将隐式使用名为 =<> 的运算符。依赖默认运算符类的新行为避免了对具有一定名称的运算符的行为做出任何假设。

可以通过在 USING 选项中指定类的小于运算符,以按非默认 B 树运算符类进行排序,例如

SELECT * FROM mytable ORDER BY somecol USING ~<~;

或者,在 USING 中指定类的大于运算符将选择降序排序。

对用户定义类型数组的比较也依赖于该类型默认 B 树运算符类定义的语义。如果没有默认 B 树运算符类,但有默认哈希运算符类,那么支持数组相等性,但不支持排序比较。

需要更多数据类型特定知识的另一项 SQL 特性是对窗口函数的 RANGE offset PRECEDING/FOLLOWING 框架选项(见 Section 4.2.8)。对于诸如以下查询:

SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
  FROM mytable;

知道如何按 x 排序是不够的;数据库还必须了解如何“从 x 的当前行值中减去 5”或“加上 10”以识别当前窗口框架的边界。将结果边界与 x 的其他行值进行比较可以使用 B 树运算符类提供的比较运算符来完成,该运算符类定义了 ORDER BY 排序,但加法和减法运算符不是运算符类的一部分,所以应该使用哪种运算符?硬编码该选择是不合适的,因为不同的排序顺序(不同的 B 树运算符类)可能需要不同的行为。因此,B 树运算符类可以指定一个 in_range 支持函数,该函数封装了与其排序顺序相符的加法和减法行为。它甚至可以提供多个 in_range 支持函数,以防有多个数据类型可以作为 RANGE 子句中的偏移量。如果与窗口的 ORDER BY 子句关联的 B 树运算符类没有匹配的 in_range 支持函数,则 RANGE offset PRECEDING/FOLLOWING 选项不受支持。

另一重要的一点是,出现在散列运算符族中的等运算符是散列连接、散列聚合及相关优化的候选者。此处散列运算符族必不可少,因为它可识别要使用的散列函数。

38.16.7. Ordering Operators #

有些索引访问方法(目前仅有 GiST 和 SP-GiST)支持 ordering operators 的概念。到目前为止,我们一直在讨论的是 search operators。搜索运算符是指可针对索引进行搜索的运算符,以查找满足 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 的操作。此声明表明该索引能够按照 <→ 运算符增大值返回有序行。

38.16.8. Special Features of Operator Classes #

运营商类别有两项特殊功能尚未讨论,主要是因为它们对于最常用的索引方法不是有用的。

通常,将运算符声明为运算符类(或族)的成员意味着索引方法可以使用该运算符获取精确满足 WHERE 条件的行集。例如:

SELECT * FROM table WHERE integer_column < 4;

可以使用整数列上的 B 树索引精确满足。但在某些情况下,索引可用作匹配行的非精确指南。例如,如果 GiST 索引仅存储几何对象的外接框,那么它无法精确满足 WHERE 条件,该条件测试非矩形对象(如多边形)之间的重叠。不过,我们可以使用索引找到其外接框与目标对象的外接框重叠的对象,然后仅对索引找到的对象执行精确的重叠测试。如果此场景适用,则该索引对于该运算符来说是“有损失”的。索引方法通过在行可能或可能确实满足查询条件时返回 recheck 标志来实现有损索引搜索。然后,核心系统将针对检索到的行测试原始查询条件以查看是否应该将其作为有效的匹配返回。如果索引保证返回所有必需的行以及通过执行原始运算符调用可以消除的一些附加行,那么这种方法就有效。支持有损搜索的索引方法(目前为 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 compressdecompress 支持例程必须处理数据类型转换。同样,当存储类型不同时,SP-GiST 也需要一个 compress 支持函数来转换到存储类型;如果 SP-GiST opclass 也支持检索数据,则反向转换必须由 consistent 函数来处理。在 GIN 中,STORAGE 类型标识“键”值类型,该类型通常不同于被索引列的类型——例如,整数数组列的运算符类可能具有仅为整数的键。GIN extractValueextractQuery 支持例程负责从索引值中提取键。BRIN 与 GIN 类似:STORAGE 类型标识存储的摘要值类型,运算符类的支持程序负责正确解释摘要值。