2024 年 9 月 26 日: PostgreSQL 17 发布!
支持版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

36.16. 将扩展与索引关联 #

到目前为止,我们介绍了如何定义新的类型、新的函数和新的运算符。但是,我们还不能在新数据类型列上定义索引。为此,我们必须为新数据类型定义一个运算符类。在本节后面的内容中,我们将通过一个示例来演示这个概念:一个针对 B 树索引方法的新运算符类,该方法以升序绝对值顺序存储和排序复数。

运算符类可以分组为运算符族,以显示语义上兼容的类之间的关系。当只涉及单个数据类型时,运算符类就足够了,因此我们将首先关注这种情况,然后回到运算符族。

36.16.1. 索引方法和运算符类 #

运算符类与索引访问方法相关联,例如B 树GIN。可以使用 CREATE ACCESS METHOD 定义自定义索引访问方法。有关详细信息,请参见第 62 章

索引方法的例程本身并不知道索引方法将操作的任何数据类型。相反,一个运算符类标识索引方法需要使用的一组运算符,以处理特定数据类型。运算符类之所以这样命名,是因为它们指定的一件事就是WHERE 子句中可以与索引一起使用的运算符集(即可以转换为索引扫描限定符)。运算符类还可以指定一些支持函数,这些函数是索引方法的内部操作所需的,但不直接对应于可与索引一起使用的任何WHERE 子句运算符。

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

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

36.16.2. 索引方法策略 #

与运算符类关联的运算符由策略编号标识,这些编号用于标识每个运算符在其运算符类上下文中的语义。例如,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 节 中讨论。)

36.16.3. 索引方法支持例程 #

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

与策略一样,运算符类标识了哪些特定函数应该为给定的数据类型和语义解释扮演这些角色。索引方法定义了其所需的函数集,而运算符类通过将这些函数分配给索引方法指定的支持函数编号 来标识要使用的正确函数。

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

B 树需要一个比较支持函数,并且允许在操作符类作者选择的情况下提供四个额外的支持函数,如表 36.9所示。这些支持函数的要求在第 64.1.3 节中进一步解释。

表 36.9. B 树支持函数

函数 支持编号
比较两个键并返回一个小于零、零或大于零的整数,指示第一个键是否小于、等于或大于第二个键 1
返回 C 可调用排序支持函数的地址(可选) 2
将测试值与基值加减偏移量进行比较,并根据比较结果返回真或假(可选) 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 支持函数

函数 描述 支持编号
一致性 确定键是否满足查询限定符 1
联合 计算一组键的联合 2
压缩 计算要索引的键或值的压缩表示(可选) 3
解压缩 计算压缩键的解压缩表示(可选) 4
惩罚 计算将新键插入到具有给定子树键的子树中的惩罚 5
选取拆分 确定要移动到新页面的页面的哪些条目以及计算结果页面的联合键 6
相同 比较两个键并返回真如果它们相等 7
距离 确定键到查询值的距离(可选) 8
提取 计算压缩键的原始表示以用于仅索引扫描(可选) 9
选项 定义特定于此操作符类的选项(可选) 10
排序支持 提供要在快速索引构建中使用的排序比较器(可选) 11

SP-GiST 索引有六个支持函数,其中一个可选,如表 36.12所示。(有关更多信息,请参见第 64.3 节。)

表 36.12. SP-GiST 支持函数

函数 描述 支持编号
配置 提供有关操作符类的基本信息 1
选择 确定如何将新值插入内部元组 2
选取拆分 确定如何对一组值进行分区 3
内部一致性 确定需要搜索哪些子分区才能进行查询 4
叶子一致性 确定键是否满足查询限定符 5
选项 定义特定于此操作符类的选项(可选) 6

GIN 索引有七个支持函数,其中四个是可选的,如表 36.13所示。(有关更多信息,请参见第 64.4 节。)

表 36.13. GIN 支持函数

函数 描述 支持编号
比较 比较两个键并返回一个小于零、零或大于零的整数,指示第一个键是否小于、等于或大于第二个键 1
提取值 从要索引的值中提取键 2
提取查询 从查询条件中提取键 3
一致性 确定值是否匹配查询条件(布尔变体)(如果存在支持函数 6 则可选) 4
部分比较 比较来自查询的部分键和来自索引的键,并返回一个小于零、零或大于零的整数,指示 GIN 应该忽略此索引条目,将条目视为匹配,或者停止索引扫描(可选) 5
三元一致性 确定值是否匹配查询条件(三元变体)(如果存在支持函数 4 则可选) 6
选项 定义特定于此操作符类的选项(可选) 7

BRIN 索引有五个基本支持函数,其中一个是可选的,如表 36.14所示。基本函数的某些版本需要提供其他支持函数。(有关更多信息,请参见第 64.5.3 节。)

表 36.14. BRIN 支持函数

函数 描述 支持编号
opcInfo 返回描述索引列的摘要数据的内部信息 1
添加值 将新值添加到现有的摘要索引元组中 2
一致性 确定值是否匹配查询条件 3
联合 计算两个摘要元组的联合 4
选项 定义特定于此操作符类的选项(可选) 5

与搜索操作符不同,支持函数返回特定索引方法期望的任何数据类型;例如,在 B 树的比较函数的情况下,一个带符号整数。每个支持函数的参数数量和类型也取决于索引方法。对于 B 树和散列,比较和散列支持函数采用与操作符类中包含的操作符相同 的输入数据类型,但对于大多数 GiST、SP-GiST、GIN 和 BRIN 支持函数则并非如此。

36.16.4. 示例 #

现在我们已经看到了这些想法,这里是我们承诺的创建新操作符类的示例。(您可以在源代码分发中的src/tutorial/complex.csrc/tutorial/complex.sql中找到此示例的工作副本。)操作符类封装了按绝对值顺序对复数进行排序的操作符,因此我们选择名称complex_abs_ops。首先,我们需要一组操作符。定义操作符的过程在第 36.14 节中讨论。对于 B 树上的操作符类,我们需要的操作符是

  • 绝对值小于(策略 1)
  • 绝对值小于或等于(策略 2)
  • 绝对值等于(策略 3)
  • 绝对值大于或等于(策略 4)
  • 绝对值大于(策略 5)

定义一组相关比较操作符最不容易出错的方法是先编写 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一词即可。

36.16.5. 操作符类和操作符族 #

到目前为止,我们一直隐式地假设一个操作符类只处理一种数据类型。虽然在一个特定的索引列中确实只能有一种数据类型,但通常对索引比较索引列和不同数据类型值的运算很有用。此外,如果在与操作符类相关的跨数据类型操作符方面有使用,则该其他数据类型通常具有与之相关的操作符类。将相关类之间的连接明确化是有帮助的,因为这可以帮助规划器优化 SQL 查询(特别是对于 B 树操作符类,因为规划器包含大量有关如何使用它们的知识)。

为了满足这些需求,PostgreSQL使用操作符族的概念。操作符族包含一个或多个操作符类,并且还可以包含可索引的操作符和相应的支持函数,这些函数属于整个族而不是族中的任何单个类。我们说这些操作符和函数在族中是松散的,而不是绑定到特定的类中。通常,每个操作符类都包含单数据类型操作符,而跨数据类型操作符在族中是松散的。

操作符族中的所有操作符和函数必须具有兼容的语义,其中兼容性要求由索引方法设置。因此,您可能想知道为什么还要费心将族的特定子集单列出来作为操作符类;实际上,对于许多目的来说,类划分无关紧要,族是唯一的有趣分组。定义操作符类的 原因是它们指定支持特定数据类型上的任何特定索引所需的族的多少。如果有一个使用操作符类的索引,那么该操作符类不能在不删除索引的情况下被删除 - 但操作符族的其他部分,即其他操作符类和松散操作符,可以被删除。因此,应该指定操作符类以包含合理地需要用于处理特定数据类型上的索引的最小操作符和函数集,然后可以将相关的非必需操作符添加为操作符族的松散成员。

例如,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 树运算符族中,族中的所有运算符必须按兼容方式排序,如第 64.1.2 节 中详细说明。对于族中的每个运算符,必须存在一个具有与运算符相同的两个输入数据类型的支持函数。建议一个族是完整的,即对于每种数据类型的组合,都包含所有运算符。每个运算符类只应包含其数据类型的非交叉类型运算符和支持函数。

若要构建多个数据类型的哈希运算符族,必须为族支持的每种数据类型创建兼容的哈希支持函数。此处兼容意味着,即使值类型不同,这些函数也保证对族等价运算符认为相等的任何两个值返回相同的哈希码。当类型具有不同的物理表示时,通常很难实现这一点,但在某些情况下可以实现。此外,通过隐式或二进制强制转换将一个值从运算符族中表示的一种数据类型转换为运算符族中也表示的另一种数据类型,不能改变计算出的哈希值。请注意,每个数据类型只有一个支持函数,而不是每个等价运算符只有一个。建议一个族是完整的,即为每种数据类型的组合提供等价运算符。每个运算符类只应包含其数据类型的非交叉类型等价运算符和支持函数。

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

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

注意

PostgreSQL 8.3 之前,没有运算符族概念,因此任何旨在与索引一起使用的跨数据类型运算符都必须直接绑定到索引的运算符类中。虽然这种方法仍然有效,但它已过时,因为它使索引的依赖关系过于广泛,并且因为当两种数据类型在同一个运算符族中都具有运算符时,规划器可以更有效地处理跨数据类型比较。

36.16.6. 系统对运算符类的依赖关系 #

PostgreSQL 使用运算符类以更多方式推断运算符的属性,而不仅仅是它们是否可以与索引一起使用。因此,即使您不打算对数据类型的任何列进行索引,您可能也希望创建运算符类。

特别是,存在诸如 ORDER BYDISTINCT 之类的 SQL 功能,这些功能需要对值进行比较和排序。若要在用户定义的数据类型上实现这些功能,PostgreSQL 会查找该数据类型的默认 B 树运算符类。此运算符类的 等于 成员定义了系统对 GROUP BYDISTINCT 的值相等的认识,运算符类强加的排序顺序定义了默认的 ORDER BY 排序。

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

当数据类型没有默认运算符类时,如果您尝试对数据类型使用这些 SQL 功能,将会收到类似 无法识别排序运算符 的错误。

注意

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

可以通过在 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 支持函数,该函数封装了对其排序顺序有意义的加法和减法行为。它甚至可以提供多个 in_range 支持函数,以防在 RANGE 子句中使用作为偏移量的多种数据类型。如果与窗口的 ORDER BY 子句关联的 B 树运算符类没有匹配的 in_range 支持函数,则不支持 RANGE offset PRECEDING/FOLLOWING 选项。

另一个重要的一点是,出现在哈希运算符族中的等价运算符是哈希连接、哈希聚合和相关优化的候选对象。哈希运算符族在这里至关重要,因为它标识要使用的哈希函数。

36.16.7. 排序运算符 #

某些索引访问方法(目前只有 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 索引可以有效地做到这一点,因为 <-> 是一个排序运算符。

虽然搜索运算符必须返回布尔结果,但排序运算符通常返回其他类型,例如浮点数或数值(用于距离)。此类型通常与被索引的数据类型不同。为了避免对不同数据类型行为的硬编码假设,排序运算符的定义需要命名一个 B 树运算符族,该族指定结果数据类型的排序顺序。如前一节所述,B 树运算符族定义了 PostgreSQL 对排序的认识,因此这是一个自然的表示。由于点 <-> 运算符返回 float8,因此可以在运算符类创建命令中指定它,如下所示

OPERATOR 15    <-> (point, point) FOR ORDER BY float_ops

其中 float_ops 是内置的运算符族,其中包含对 float8 的操作。此声明指出索引能够按 <-> 运算符值的递增顺序返回行。

36.16.8. 运算符类的特殊功能 #

运算符类有两个特殊功能,我们尚未讨论,主要是因为它们对最常用的索引方法没有用。

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

SELECT * FROM table WHERE integer_column < 4;

如果整数列上有一个 B 树索引,则可以完全满足条件。但在某些情况下,索引对于匹配行只是一个不精确的指南。例如,如果一个 GiST 索引只存储几何对象的边界框,那么它不能完全满足 WHERE 条件,该条件测试非矩形对象(如多边形)之间的重叠。但是,我们可以使用该索引查找其边界框与目标对象边界框重叠的对象,然后仅对索引找到的对象进行精确的重叠测试。如果出现这种情况,则该索引被认为对于该操作符是“有损的。有损索引搜索通过让索引方法在行可能或可能不真正满足查询条件时返回一个 recheck 标志来实现。然后,核心系统将在检索到的行上测试原始查询条件,以查看它是否应该作为有效匹配返回。如果索引保证返回所有必需的行,以及可能的一些额外的行,这些行可以通过执行原始操作符调用来消除,则这种方法有效。支持有损搜索的索引方法(目前,GiST、SP-GiST 和 GIN)允许单个操作符类的支持函数设置 recheck 标志,因此这本质上是一个操作符类特性。

再次考虑我们只在索引中存储复杂对象(如多边形)的边界框的情况。在这种情况下,在索引条目中存储整个多边形没有多大价值——我们不妨只存储一个更简单的 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 操作符类也支持检索数据时,SP-GiST 也需要一个 compress 支持函数来转换为存储类型,如果需要进行反向转换,则必须由 consistent 函数处理。在 GIN 中,STORAGE 类型标识“ 值的类型,这通常与索引列的类型不同——例如,用于整数数组列的操作符类可能具有只是整数的键。GIN extractValueextractQuery 支持例程负责从索引值中提取键。BRIN 与 GIN 类似:STORAGE 类型标识存储的摘要值的类型,操作符类的支持过程负责正确解释摘要值。

提交更正

如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表格 报告文档问题。