pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
到目前为止所描述的过程使我们能够定义新类型、新函数以及新操作符。然而,我们还不能在一种新数据类型的列上定义索引。为此,必须为该新数据类型定义一个操作符类。本节稍后将用一个示例说明这一概念:为 B-树索引方法定义一个新的操作符类,以便按绝对值升序存储和排序复数。
在 PostgreSQL 7.3 版本之前,要创建用户定义的操作符类, 必须手工向系统目录 pg_amop、pg_amproc 和 pg_opclass 中添加内容。这种方式现已废弃,改为使用 CREATE OPERATOR CLASS,它是创建所需目录项的一种简单得多、 也不易出错的方式。
pg_am 表为每个索引方法(内部称为访问方法)保存一行。对表进行常规访问的支持内置于 PostgreSQL 中,但所有索引方法都在 pg_am 中描述。可以定义所需的接口例程,然后在 pg_am 中创建一行,从而添加新的索引方法 — 但这远超出了本章的范围。
索引方法的例程并不直接知道它将处理哪些数据类型。相反,一个操作符类标识了索引方法在处理特定数据类型时需要使用的那组操作。之所以称为操作符类,是因为它指定的一项内容就是可与索引一起使用的 WHERE 子句操作符集合(也就是能被转换成索引扫描条件的操作符)。操作符类还可以指定索引方法内部操作所需的某些支持过程,但这些过程并不直接对应任何可与索引一起使用的 WHERE 子句操作符。
可以为同一种数据类型和索引方法定义多个操作符类。这样就能为一种数据类型定义多套索引语义。例如,一个 B-树索引要求为其处理的每一种数据类型定义一种排序顺序。对于复数数据类型,也许既需要一个按复数绝对值排序的 B-树操作符类,也需要另一个按实部排序的操作符类,等等。通常,其中一个操作符类会被视为最常用,并标记为该数据类型在该索引方法上的默认操作符类。
同一个操作符类名可以用于多个不同的索引方法(例如,B-树和哈希索引方法都有名为 oid_ops 的操作符类),但每一个这样的类都是独立实体,必须分别定义。
与操作符类关联的操作符通过“策略号”来标识,用以表示每个操作符在其操作符类上下文中的语义。例如,B-树对键施加了严格的从小到大的顺序,因此像“小于”和“大于等于”这样的操作符,对 B-树来说就很重要。由于 PostgreSQL 允许用户定义操作符,PostgreSQL 不能仅凭操作符名称(例如 < 或 >=)就判断它是哪一类比较。取而代之的是,索引方法定义了一组“策略”,可以把它们看成是广义的操作符。每个操作符类都会说明,对于某种特定数据类型和某种索引语义解释,每一种策略分别对应哪个实际操作符。
B-树索引方法定义了五种策略,如表 33.2所示。
表 33.2. B-树策略
| 操作 | 策略号 |
|---|---|
| 小于 | 1 |
| 小于等于 | 2 |
| 等于 | 3 |
| 大于等于 | 4 |
| 大于 | 5 |
哈希索引只支持等值比较,因此它们只使用一种策略,如表 33.3所示。
表 33.3. 哈希策略
| 操作 | 策略号 |
|---|---|
| 等于 | 1 |
R-树索引表达矩形包含关系。它们使用八种策略,如表 33.4所示。
表 33.4. R-树策略
| 操作 | 策略号 |
|---|---|
| 位于左侧 | 1 |
| 位于左侧或重叠 | 2 |
| 重叠 | 3 |
| 位于右侧或重叠 | 4 |
| 位于右侧 | 5 |
| 相同 | 6 |
| 包含 | 7 |
| 被包含 | 8 |
GiST 索引更加灵活:它们根本没有固定的策略集合。相反,每个特定 GiST 操作符类的“一致性”支持例程会按自己的方式解释策略号。
注意,所有策略操作符都返回布尔值。实际上,所有被定义为索引方法 策略的操作符都必须返回 boolean 类型,因为要与索引 配合使用,它们必须出现在 WHERE 子句的顶层。
顺便说一下,pg_am 中的 amorderstrategy 列表明索引方法是否支持有序扫描。 零表示不支持;如果支持,amorderstrategy 就是与排序操作符对应的策略号。例如,B-树的 amorderstrategy = 1,即它的“小于”策略号。
仅靠策略信息通常不足以让系统知道如何使用索引。实际上,索引方法还需要额外的支持例程才能工作。例如,B-树索引方法必须能够比较两个键,并判断其中一个是大于、等于还是小于另一个。类似地,R-树索引方法必须能够计算矩形的交集、并集和大小。这些操作并不对应 SQL 命令条件中使用的操作符;它们是索引方法内部使用的管理例程。
与策略一样,操作符类会标识对于给定的数据类型和语义解释,应由哪些具体函数承担这些角色。索引方法定义它需要的函数集合,而操作符类则会通过为函数分配由索引方法规定的“支持函数号”来标识正确的函数。
B-树要求一个支持函数,如 表 33.5 所示。
表 33.5. B-树支持函数
| 函数 | 支持号 |
|---|---|
| 比较两个键,并返回一个小于零、等于零或大于零的整数,用以 表示第一个键是小于、等于还是大于第二个键 | 1 |
哈希索引同样只需要一个支持函数,如 表 33.6 所示。
表 33.6. 哈希支持函数
| 函数 | 支持号 |
|---|---|
| 计算一个键的哈希值 | 1 |
R-树索引要求三个支持函数,如 表 33.7 所示。
表 33.7. R-树支持函数
| 函数 | 支持号 |
|---|---|
| union | 1 |
| intersection | 2 |
| size | 3 |
GiST 索引要求七个支持函数,如 表 33.8 所示。
表 33.8. GiST 支持函数
| 函数 | 支持号 |
|---|---|
| consistent | 1 |
| union | 2 |
| compress | 3 |
| decompress | 4 |
| penalty | 5 |
| picksplit | 6 |
| equal | 7 |
与策略操作符不同,支持函数返回的是特定索引方法所期望的数据 类型;例如,对 B-树的比较函数来说,就是一个有符号整数。
现在我们已经了解了这些基本思想,下面给出先前承诺的创建新操作符类示例。(这个可运行示例位于源码发布包中的src/tutorial/complex.c和src/tutorial/complex.sql。)该操作符类封装了一组按绝对值顺序对复数排序的操作符,因此我们把它命名为complex_abs_ops。首先,我们需要一组操作符。定义操作符的过程已经在第 33.11 节中讨论过。对于 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 函数名中包含数据类型名称是个好习惯,这样就不会与其他数据类型的函数发生冲突。
我们原本也可以把该函数的 PostgreSQL 名称取为 abs_eq,并依靠 PostgreSQL 通过参数数据类型把它与任何其他同名的 PostgreSQL 函数区分开。为了让示例保持简单,这里我们让 C 层和 PostgreSQL 层的函数使用相同的名称。
下一步是注册 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 这个词。
PostgreSQL利用操作符类来从多方面推断操作符的属性,而不仅仅是判断它们能否用于索引。因此,即便你并不打算为自己的数据类型列建立索引,也可能会想创建操作符类。
特别地,ORDER BY和DISTINCT等 SQL 特性要求对值的比较和排序。为了在用户定义的数据类型上实现这些特性,PostgreSQL会为数据类型查找默认 B-树操作符类。这个操作符类的“相等”成员定义了用于GROUP BY和DISTINCT的值的等值概念,而该操作符类施加的排序顺序定义了默认的ORDER BY顺序。
用户定义类型的数组比较也依赖于该类型默认 B-树操作符类所定义的语义。
如果一种数据类型没有默认的 B-树操作符类,系统就会查找默认的哈希操作符类。但由于这类操作符类只提供等值语义,因此在实践中它只足以支持数组相等比较。
如果某种数据类型没有默认操作符类,而你又试图将这些 SQL 特性用于该数据类型,就会得到类似“无法识别排序操作符”这样的错误。
在版本 7.4 以前的PostgreSQL中,排序和分组操作将隐式地使用名为=、<以及>的操作符。新的依赖于默认操作符类的行为避免了对具有特定名字的操作符行为作出任何假设。
还有两个操作符类的特殊特性我们尚未讨论,主要是因为它们对最常用的索引方法没有用处。
通常,把一个操作符声明为操作符类的成员,意味着 索引方法能够正好检索出满足使用该操作符的 WHERE 条件的行集。例如:
SELECT * FROM table WHERE integer_column < 4;
可以由整数列上的 B-tree 索引精确满足。但也存在这样的情况:索引 只是查找匹配行的不精确向导。例如,如果一个 R-树索引只存储对象的包围盒,那么它就无法精确满足测试多边形 等非矩形对象之间重叠的 WHERE 条件。不过,我们可以用 索引找出包围盒与目标对象的包围盒重叠的对象,然后只对索引找到的 对象做精确的重叠测试。如果适用这种情形,就称该索引对该操作符是 “有损”的,此时我们在 CREATE OPERATOR CLASS 命令的 OPERATOR 子句中加上 RECHECK。 如果索引保证返回所有需要的行(可能还外加一些额外的行,它们可以 通过执行原始操作符调用来排除),RECHECK 就是合法的。
再次考虑只在索引中存储多边形等复杂对象的包围盒的情况。此时,在索引条目中存储整个多边形没有多少价值,不如只存储一个更简单的对象,其类型为box。这种情况由STORAGE选项表达,该选项位于CREATE OPERATOR CLASS:可以写成如下形式:
CREATE OPERATOR CLASS polygon_ops
DEFAULT FOR TYPE polygon USING gist AS
...
STORAGE box;
目前,只有 GiST 索引方法支持与列数据类型不同的STORAGE类型。GiST 的compress和decompress支持例程在使用STORAGE时必须处理数据类型转换。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。