33.13. 扩展索引接口

到目前为止我们描述的过程可以让你定义一个新类型,新函数和新操作符. 但是,我们还不能在一个新数据类型的字段上面定义一个索引。为了做这 些事情,我们必须为新数据类型定义一个 操作符表。 我们讲在一个真实的例子的环境中来描述操作符表: 一个用于 B-tree 访问方法的新的操作符表,它保存复数并对之以绝对值 递增的顺序排序。

注意: PostgreSQL 版本7.3之前,我们必须 手工给系统表 pg_amoppg_amproc,和 pg_opclass 添加记录,以便于创建用户定义操作符表。 现在这个方法已经废弃了,因为我们有了 CREATE OPERATOR CLASS, 它在创建必要的表记录的时候更简单并且更不容易出错。

33.13.1. 索引方法和操作符表

pg_am 表为每个索引方法(内部称作访问方法)都包含一条记录. 对表的普通访问方法的支持内建于 PostgreSQL,但所有访问方法在 pg_am 里都有描述. 我们可以通过定义要求的接口过程并在 pg_am 里创建一个行的办法增加一个索引访问方法 --- 但那些远远超出了本章 的内容。

一个索引方法的过程并不直接知道任何该索引方法将要操作的 数据类型的信息。而是操作符表表明索引方法在操作特定 数据类型的时候需要使用的操作集合。操作符表的名称由来是因为它们 声明的一件事情是一种索引可以使用的 WHERE 子句的操作符集(也就是说 可以转化成一个索引扫描条件)。一个操作符表也可以声明一些索引 方法需要的内部操作的支持过程,但是它们并不直接和可以 与索引一起使用的 WHERE 子句操作符相关。

我们可以为同一个数据类型和索引方法定义多个操作符表。 这么做的结果是,我们可以为一种数据类型定义多套索引语义。比如, 一个 B-tree 索引要求为它操作的每种数据类型定义一个排序顺序。 对于一个复数数据类型而言,有一个通过复数绝对值对数据排序的 B-tree 操作符表可能会有用,还有一个是用实部排序,等等。 通常其中一个操作符表会被认为最常用的,并且被标记为该数据类型和索引 方法的缺省操作符表。

同样的操作符表名字可以用于多种不同的索引方法(比如,B-tree 和散列访问 方法都有叫 oid_ops 的操作符表),但是每个这样的 表都是一个独立的实体,必须分别定义。

33.13.2. 索引方法策略

和一种操作符表相关联的操作符是通过"策略号"标识的, 策略号用于标识每种操作符在它的操作符表环境里的语义。 比如,B-tree 对键字有严格的排序要求,小于到大于,因此,象"小于""大于或等于"这样的操作符都是 B-tree 所感兴趣的。 因为 PostgreSQL 允许用户定义操作符, PostgreSQL 无法查看操作符的名字 (比如,<>=)就明白它进行的比较是什么。 实际上,索引索引方法定义了一套"策略",它可以看作时一般性的操作符。 每种操作符表显示对于特定数据类型,是哪种实际操作符对应每种策略, 以及解释索引的语义。

B-tree 索引定义了五种策略.在 Table 33-2 中显示。

Table 33-2. B-tree 策略

操作索引
小于1
小于或等于2
等于3
大于或等于4
大于5

散列索引只表示按位的相等,因此它们只定义了一个策略, 在 Table 33-3 里显示。

Table 33-3. 散列索引

操作策略号
等于1

R-tree 索引表达方形包含关系。它们定义了八个策略, 在 Table 33-4 里显示。

Table 33-4. R-tree 策略

操作策略号
在...左边1
在...左边或者重叠2
重叠3
在...右边或者重叠4
在...右边5
相同6
包含7
被包含8

GiST 索引甚至更加灵活:它们根本就没有固定地策略集。实际上,是 每个特定 GiST 操作符表的"一致性"支持过程解释策略号是什么样子。

请注意,所有策略操作符都返回布尔值。实际上,所有定义为索引方法 策略的操作符都必须返回类型boolean,因为它们必须出现在 一个 WHERE 子句的顶层,这样才能被一个索引使用。

顺便提一下,pg_am 里的 amorderstrategy 字段告诉我们该索引方法是否支持排序的扫描。零意味着它不支持; 如果它支持,amorderstrategy 是对应该排序操作符 的策略号。比如,B-tree 的 amorderstrategy = 1, 是它的"小于"的策略号。

33.13.3. 索引方法支持过程

有时候,策略的信息还不足以让系统决定如何使用某个索引. 实际上,索引方法就需要附加的一些过程来保证能够工作. 例如,B-tree 索引方法必须能够比较两个键字以决定其中一个是大于,等于, 还是小于另外一个. 类似的还有 R-tree 索引方法必须能够计算长方形的相交, 并,和大小等.这些操作和 SQL 命令条件里使用的操作符并不对应; 它们是被索引方法的管理性质的过程内部调用的过程.

就像策略一样,操作符表声明在一定的数据类型和语义解释的条件下, 哪个特定函数对应这些角色中的哪一个。索引方法声明它需要的函数集, 儿操作符表通过给它们赋予"支持函数编号"来标识要使用的正确的函数。

B-tree 需要一个支持函数,在 Table 33-5 里显示。

Table 33-5. B-tree 支持函数

函数支持号
比较两个键字并且返回一个小于零,零,或者大于零的整数, 标识第一个键字是小于,等于还是大于第二个键字。 1

类似的是散列索引也需要一个支持函数,在 Table 33-6 里显示。

Table 33-6. 散列支持函数

函数支持号
为一个键字比较散列值1

R-tree 索引需要三个支持函数,在 Table 33-7 里显示。

Table 33-7. R-tree 支持函数

函数支持号
联合1
2
大小3

Gist 索引需要七种支持函数,在 Table 33-8 里显示。

Table 33-8. GiST 支持函数

函数支持号
一致性1
联合2
压缩3
解压缩4
处罚5
选择分裂6
等于7

和策略操作符不同,支持函数返回特定索引方法预期的数据类型,比如在 B-tree 的情况下,返回一个符号整数。

33.13.4. 例子

既然我们已经了解了这些概念,那么现在就是我们承诺过的创建一个 新的操作符表的例子。 操作符表封装了那些以绝对值顺序对复数排序的操作符,这样我们 就可以选择 complex_abs_ops 这个名字。 首先,我们需要一个操作符集合。 用于定义操作符的过程已经在Section 33.11讨论过了. 对这个用于 B-tree 的 complex_abs_ops 操作符表, 我们需要的操作符是:

用于相等操作的 C 代码看起来像这样:

#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y)

bool
complex_abs_eq(Complex *a, Complex *b)
{
    double amag = Mag(a), bmag = Mag(b);
    return (amag==bmag);
}

其它四个操作符非常类似.你可以在源代码的 src/tutorial/complex.csrc/tutorial/complex.sql 获取代码。

然后基于这些函数声明函数和操作符:

CREATE FUNCTION complex_abs_eq(complex, complex) RETURNS boolean
    AS 'filename', 'complex_abs_eq'
    LANGUAGE C;

CREATE OPERATOR = (
    leftarg = complex,
    rightarg = complex,
    procedure = complex_abs_eq,
    restrict = eqsel,
    join = eqjoinsel
);

声明限制和连接选择性函数是非常重要的,否则优化器将无法有效地 利用索引。请注意,小于,等于和大于的情况下应该使用不同的选择性 函数。

其它几个值得一提的问题问题要在这里出现∶

下一步是注册 B-tree 需要的比较"支持过程"。 实现这个的例子 C 代码在包含操作符过程的同一个文件中, 下面是定义函数的方法:

CREATE FUNCTION complex_abs_cmp(complex, complex)
    RETURNS integer
    AS 'filename'
    LANGUAGE C;

既然我们已经有了需要的操作符何支持过程, 我们就可以最后创建这个操作符表了:

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-tree 索引了.

我们可以把操作符记录写得更冗余一些,象

        OPERATOR        1       < (complex, complex) ,

但是如果该操作符接受的数据类型是我们定义的操作符表处理的东西, 那就没必要这么做。

上面的例子假设你象把这个新操作符表作为 complex 数据类型 的缺省的 B-tree 操作符表。如果你不想这么做,只要去掉关键字 DEFAULT 就行了。

33.13.5. 操作符表的特殊特性

还有两种操作符表的特殊特性我们没有讨论,主要是因为它们 对于缺省的 B-tree 索引索引方法并不非常有用。

通常,把一个操作符声明为一个操作符表的成员意味着索引索引方法 可以使用该操作符检索满足一个 WHERE 条件的行集合。比如,

SELECT * FROM table WHERE integer_column < 4;

可以由一个建立在整数字段上的 B-tree 索引精确地满足。但是有时候 会有这样的现象:索引是用作匹配数据行的并不精确的指向。比如, 如果一个 R-tree 索引只为对象存储周界的方块,那么它就无法精确地 满足一个两个非方形对象(比如多边形)之间是否覆盖的WHERE条件的测试。 但是我们可以使用这个索引找出那些周界方块和目标对象的周界方块 重合的对象,然后只在索引找到的对象上做精确的重合测试。如果这种 情形可以通过,那我们就说索引对操作符是"松散的", 并且我们在 CREATE OPERATOR CLASS 命令里给 OPERATOR 子句增加 RECHECK。如果索引保证 返回所有要求的元组加上一些附加的行,那么 RECHECK 就合法, 这些额外的元组就可以通过执行最初的操作符调用消除。

再考虑我们再索引中只存储复杂对象(比如多边形)的周界方块的情形。 这种情况下我们在索引条目里存储整个多边形没有太多的数值 --- 我们 也可以只存储更简单的类型 box 的对象。 这种情形由 CREATE OPERATOR CLASS 里的 STORAGE 选项存储:我们可以写类似这样的东西

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

目前,只有 GiST 索引方法支持与字段数据类型不同的 STORAGE 类型。 GiST compressdecompress 支持过程 在使用 STORAGE 的时候必须处理数据类型转换。