35.14. 扩展索引接口

到目前为止描述的过程可以让你定义一个新类型、新函数、新操作符。但是, 还不能在一个新数据类型的字段上面定义一个索引。为了达到这个目的, 必须为新数据类型定义一个操作符类。下面将使用一个真实的例子来描述操作符类: 一个用于 B-tree 访问方法的新操作符类,它保存复数并按照绝对值递增的顺序排序。

操作符类可以分类到操作符族,用以显示语义兼容的类之间的关系。 当只包含一个数据类型时,一个操作符类就足够了,所以我们首先关注这种情况, 然后再转到操作符族。

35.14.1. 索引方法和操作符类

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

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

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

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

35.14.2. 索引方法策略

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

B-tree 索引定义了五种策略。在表 35-2中显示。

表 35-2. B-tree 策略

操作策略号
小于1
小于或等于2
等于3
大于或等于4
大于5

Hash 索引只支持平等的比较,因此它们只定义了一个策略, 在表 35-3里显示。

表 35-3. Hash 策略

操作策略号
等于1

GiST 索引甚至更加灵活:它们根本就没有固定的策略集。实际上, 是每个特定 GiST 操作符类的"一致性"支持过程解释策略号是什么样子。作为示例, 有几个内置的 GiST 索引操作符类索引二维几何对象,提供表 35-4 中所示的"R-tree"策略。其中的四个是两维测试(重叠、相同、包含、包含于); 四个只考虑 x 坐标、四个对 y 坐标进行同样测试。

表 35-4. GiST 两维"R-tree"策略

操作策略号
严格地在...左边1
不扩展到...右边2
重叠3
不延伸到...左边4
严格地在...右边5
相同6
包含7
包含于8
不扩展到...上面9
严格地在...下面10
严格地在...上面11
不扩展到...下面12

SP-GiST索引在灵活性方面与 GiST 索引类似:它们都没有一个固定的策略集, 而是由每个操作符类的支持过程根据操作符类的定义来解释策略号。作为示例, 表 35-5显示了内置的点操作符类使用的策略号。

表 35-5. SP-GiST 点策略

操作策略号
严格在左边1
严格在右边5
相同6
包含8
严格在下面10
严格在上面11

GIN 索引与 GiST 索引和SP-GiST 索引类似:它们都没有一个固定的策略集, 而是由每个操作符类的支持过程根据操作符类的定义来解释策略号。作为示例, 表 35-6显示了内置的数组操作符类使用的策略号。

表 35-6. GIN 数组策略

操作策略号
重叠1
包含2
包含于3
相等4

请注意,所有上述操作符都返回布尔值。实际上,所有定义为索引方法搜索操作符的操作符都必须返回 boolean类型,因为它们必须出现在一个WHERE子句的顶层, 这样才能被一个索引使用。(某些索引访问方法也支持顺序操作符, 它们通常不反悔Boolean值;这个特征在第 35.14.7 节里讨论。)

35.14.3. 索引方法支持过程

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

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

B-tree 需要一个支持函数,并且允许在操作符类作者的选项中提供第二个, 就像表 35-7里显示的那样。

表 35-7. B-tree 支持函数

函数支持号
比较两个键字并且返回一个小于、等于、大于零的整数,标识第一个键字小于、 等于、大于第二个键字。 1
返回C-callable排序支持函数的地址,记录在utils/sortsupport.h(可选) 2

Hash 索引也需要一个支持函数,在表 35-8里显示。

表 35-8. Hash 支持函数

函数支持号
为一个键字计算散列值1

GiST 索引需要七种支持函数,和一个可选的函数,在表 35-9里显示。 (更多信息请参考第 56 章。)

表 35-9. GiST 支持函数

函数描述支持号
consistent检测键是否满足查询限定符1
union计算一套键的联合2
compress计算已索引键或值的压缩结果3
decompress计算已压缩键的解压结果4
penalty计算使用给定的子树的键向子树中插入新键的性能恶化(penalty)5
picksplit检测页面中的哪个项将被移动到新页面并为结果页计算联合键6
equal比较两个键并在相等时返回真7
distance确定键到查询值的距离(可选)8

SP-GiST索引需要五种支持函数,显示在表 35-10中。 (更多信息请参阅第 57 章。)

表 35-10. SP-GiST 支持函数

函数描述支持号
config提供操作符类的基本信息1
choose确定如何将一个新值插入一个内在的元组2
picksplit确定如何分区一组值3
inner_consistent确定哪个子分区需要为一个查询搜索4
leaf_consistent确定哪个键满足查询条件5

GIN 索引需要四种支持函数,和一个可选的函数,在表 35-11里显示。 (更多信息请参阅第 58 章。)

表 35-11. GIN 支持函数

函数描述支持号
compare 比较两个键并返回一个小于、等于、大于零的整数,标识第一个键小于、等于、大于第二个键。 1
extractValue从将被索引的值中抽取键2
extractQuery从查询条件中抽取键3
consistent检测值是否匹配查询条件(布尔变量)(如果支持功能6则是可选的)4
comparePartial 比较部分来自查询的键和来自索引的键,并返回一个小于、等于、大于零的整数, 标识是否GIN应该忽略这个索引项,将这个项视为一个匹配,或停止索引扫描(可选)。 5
triConsistent检测值是否匹配查询条件(三元变量)(如果支持功能4则是可选的) 6

和搜索操作符不同,支持函数返回特定索引方法预期的数据类型,比如在 B-tree 的情况下, 返回一个有符号整数。每个支持函数的参数的数字和类型也取决于索引方法。 对于B-tree和hash的比较,散列支持函数接受相同的输入数据类型,同样操作符也包含在操作符类里, 但是大多数GiST, SP-GiST, 和GIN 支持函数不是这样的。

35.14.4. 例子

既然已经了解了这些概念,那么现在就来看一个创建新操作符类的例子。你可以在源代码的 src/tutorial/complex.csrc/tutorial/complex.sql 中找到这里讲述的例子。操作符类封装了那些以绝对值顺序对复数排序的操作符,这样就可以选择 complex_abs_ops这个名字。首先,需要一个操作符集合。 用于定义操作符的过程已经在第 35.12 节讨论过了。对这个用于 B-tree 的操作符类, 需要的操作符是:

定义一组相关的比较操作符最不容易出错的方法是首先写出 B-tree 比较支持函数, 然后再写出其它封装了支持函数的单行函数。这就减少了某些情况下导致不一致结果的机会。 根据这个指引,首先写出:

#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-tree 需要的"支持过程"。实现这个例子的 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-tree 索引了。

可以把操作符记录写得更冗余一些,像:

        OPERATOR        1       < (complex, complex) ,

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

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

35.14.5. 操作符类和操作符族

到目前为止我们都隐含的假定一个操作符类只能处理一种数据类型。 虽然每个索引字段都只能是单独一种数据类型, 但是使用索引操作符来比较一个已索引字段和一个不同类型的值常常很有用处。 如果有用于与一个操作符类连接的交叉数据类型操作符,通常是其他数据类型有他自己的相关的操作符类。 这对于在相关的类之间明确的建立连接是有帮助的,因为这可以帮助规划器优化SQL查询 (尤其对于B-tree操作符类,因为规划器包含大量的关于如果处理这些问题的信息)。

为了处理这种需求,PostgreSQL使用操作符族的概念。一个操作符族包含一个或多个操作符类, 也可以包含可索引的操作符和对应的支持函数,作为一个整体属于这个族,但不是这个族中的任何一个类。 我们说这样的操作符和函数是"松散"在族里的,而不是被绑定到一个特定的类。 通常每个操作符类包含一个数据类型操作符,而交叉数据类型操作符是散落在族里的。

所有在一个操作符族里的操作符和函数必须有兼容的语法,兼容性需求是通过索引方法设置的。 你可能想知道为什么费心的挑选出特别的族的子集作为操作符类; 并且甚至为了多种目的类的区分是不相关的,族只对分组感兴趣。 定义操作符类的原因是指定多少族需要支持任何特定的索引。如果有一个索引使用一个操作符类, 然后操作符类不能在不删除索引的情况下被删除,但是操作符族的其他部分, 即其他操作符类和松散的操作符可以被删除。因此,一个操作符类应该被指定包含最少的操作符和函数, 应该是在一个特定数据类型上索引工作所需要的适当的操作符和函数, 然后相关的但非重要的操作符可以作为松散的操作符族成员添加。

作为一个例子,PostgreSQL有一个内置的B-tree操作符族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
  -- 标准 int8 比较
  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
  -- 标准 int4 比较
  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
  --标准 int2 比较
  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
  -- 交叉类型比较 int8 对 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) ,

  -- 交叉类型比较 int8 对 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) ,

  -- 交叉类型比较 int4 对 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) ,

  -- 交叉类型比较 int4 对 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) ,

  -- 交叉类型比较 int2 对 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) ,

  -- 交叉类型比较 int2 对 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-tree操作符族内,所有的操作符都必须适当的排序,意味着传递法保存所有该族支持的数据类型: "if A = B and B = C, then A = C",和"if A < B and B < C, then A < C"。 此外,代表操作符族的类型间的隐式的或二进制强制转换必须不能改变相关的排序次序。 族内的每个操作符必须有一个支持的函数,这个函数有和操作符相同的两个输入数据类型。 建议一个族是完整的,也就是,对于每个数据类型的组合,所有的操作符都包括了。 每个操作符类应该只包含非交叉类型操作符和它的数据类型的支持函数。

要建立一个多数据类型散列操作符族,必须为每个该族支持的数据类型创建兼容的散列支持函数。 这里的兼容意味着函数保证对两个通过族的相等运算符认为相等的两个值返回相同的散列码, 甚至两个值属于不容的类型时也是。当类型有不同的物理表示时这通常是很难完成的, 但是在某些情况下是可以做到的。更多的,通过隐式的或二进制强制转换, 转换一个操作符族中的数据类型的值到另一个同样在操作符族中的数据类型, 必须不能改变计算散列值。注意每个数据类型只有一个支持函数,而不是每个相等操作符。 建议一个族是完整的,也就是,对于每个数据类型的组合都提供一个相等操作符。 每个操作符类应该只包含非交叉类型相等操作符和它的数据类型的支持函数。

GiST, SP-GiST, 和 GIN索引对于交叉数据类型操作符没有任何明确的概念。 支持的操作符集对于可以处理的给定的操作符类只是主要的支持函数。

注意: PostgreSQL 8.3之前,没有操作符族的概念, 因此任何试图和索引一起使用的交叉数据类型操作符必须直接绑定到索引的操作符类里面。 虽然这种方法仍然有效,但是已经弃用了,因为它使得索引的依赖太过广泛, 并且因为当数据类型都有操作符在相同的操作符族内时,规划器可以更有效的处理交叉数据类型比较。

35.14.6. 操作符类的系统相关性

除了是否可以用于索引外,PostgreSQL还有多种途径使用操作符类来推断操作符性质。 因此,即使并不打算为你自定义的数据类型在任何字段上建立索引,你可能还是希望创建操作符类。

特别是诸如ORDER BYDISTINCT之类需要对值进行比较和排序的 SQL 特性。 要在自定义的数据类型上实现这些特性,PostgreSQL 将会为该类型查找默认的 B-tree 操作符类。该操作符类中的"equals" 成员为GROUP BYDISTINCT定义了相等的概念, 同时操作符类的排序顺序定义了默认的ORDER BY排序。

用户自定义类型数组的比较同样也依赖于默认 B-tree 操作符类定义的语意。

如果对于某个数据类型不存在默认 B-tree 操作符类,那么系统将会自动寻找默认的 Hash 操作符类。 但因为 Hash 操作符类仅仅提供相等比较,所以在实践中它仅能用于数组的相等性测试。

如果某个数据类型不存在任何缺省操作符类,你就会在使用该 SQL 特性时得到一个类似 "could not identify an ordering operator"的错误。

注意: PostgreSQL 7.4 以前,排序和分组操作隐含使用名为=, <,>的操作符。新的依赖默认操作符类的行为避免了对任何特定操作符名的行为的假定。

另一点重要的是一个在hash操作符族中的操作符是hash连接,hash聚合和相关优化的候选。 hash操作符族在这里是重要的,因为它标志要使用的hash函数。

35.14.7. 排序操作符

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

当搜索操作符必须返回布尔结果时,排序操作符通常返回一些其他类型,如float或numeric。 这种类型通常不同于被索引的类型。为了避免关于不同数据类型行为的硬链接的假设, 排序操作符的定义需要命名一个B-tree操作符族,声明结果数据类型的排序次序。 就像前一节中阐明的,B-tree操作符族定义PostgreSQL的排序概念, 所以这是一个自然的表示。因为point <->操作符返回float8, 可以在一个操作符类的创建命令中指定,像这样:

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

这里的float_ops是包含float8操作的内建操作符族。 这个说明声明了索引可以以<->操作符的增值的顺序返回行。

35.14.8. 操作符类的特殊特性

还有两种操作符类的特殊特性没有讨论,主要是因为它们对于大多数常用的索引方法并不非常有用。

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

SELECT * FROM table WHERE integer_column < 4;

可以由一个建立在整数字段上的 B-tree 索引精确地满足。但是有时候会有这样的现像: 索引是用作匹配数据行的并不精确的指向。比如,如果一个 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 和 GIN 索引方法支持与字段数据类型不同的STORAGE类型。 GiST compressdecompress 支持过程在使用 STORAGE的时候必须处理数据类型转换。对于 GIN 来说,STORAGE 类型标识了"键"值的类型,它通常与索引字段的类型不同。比如, 一个用于整数数组字段的操作符类可能正好有整数类型的键。GIN extractValueextractQuery支持过程负责从已索引的值抽取键字。