↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 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
历史版本PostgreSQL 8.0 已于 2010 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

31.13. 操作符优化信息 #

PostgreSQL 的操作符定义可以包含若干可选子句, 用来告诉系统该操作符行为的一些有用信息。只要适用,就应当提供这些子句, 因为它们可以显著加快使用该操作符的查询执行速度。但如果你提供了这些信息, 就必须确保它们是正确的!错误地使用优化子句可能导致服务器进程崩溃、输出出现隐 蔽错误,或者引发其他糟糕后果。如果你拿不准,完全可以省略某个优化子句; 唯一的后果,只是查询可能会比本来需要的更慢。

未来版本的 PostgreSQL 可能会加入更多优化子 句。这里描述的是 8.0.26 版本所理解的全部子句。

31.13.1. COMMUTATOR

如果给出 COMMUTATOR 子句,它指定一个与正在定义的操 作符互为交换子的操作符。若对所有可能的输入值 x、y,都有 (x A y) 等于 (y B x),则称操作符 A 是操作符 B 的交换子。注意,B 也同样是 A 的交换 子。例如,对某种特定数据类型而言,< 和 > 通常互为交换子,而操作符 + 通 常与其自身可交换。但操作符 - 通常并不与任何操作符可 交换。

一个可交换操作符的左操作数类型,与其交换子的右操作数类型相同,反之亦 然。因此,PostgreSQL 只需知道交换子操作符 的名称,就可以查找出该交换子,而这也正是 COMMUTATOR 子句中所需提供的全部内容。

对于将用于索引和连接子句的操作符,提供交换子信息至关重要,因为这样查询 优化器才能把这类子句“翻转”成不同计划类型所需的形式。例 如,考虑这样一个查询,其 WHERE 子句形如 tab1.x = tab2.y,其中 tab1.x 和 tab2.y 都是某种用户定义类型,并假设 tab2.y 上建有索引。除非优化器能够确定如何把该子句翻 转成 tab2.y = tab1.x,否则它就无法生成索引扫描,因为 索引扫描机制要求传给它的操作符左侧必须是已建立索引的列。 PostgreSQL 不会仅凭假 设就认为这种转换有效;= 操作符的创建者必须通过为该操 作符标记交换子信息,明确声明这种转换是有效的。

定义一个与自身可交换的操作符时,直接定义即可。但定义一对可交换操作符时,情况就稍微复杂一些:第一个要定义的操作符如何引用另一个尚未定义的操作符呢?这个问题有两种解决办法:

  • 一种办法是在定义第一个操作符时省略 COMMUTATOR 子句,然后在第二个操作符的定义中提供该子句。由于 PostgreSQL 知道可交换操作符是成对出现的,因此它在看到第二个定义时,会自动回头补全第一个定义中缺少的 COMMUTATOR 子句。

  • 另一种更直接的办法是在两个定义中都包含 COMMUTATOR 子句。当 PostgreSQL 处理第一个定义并发现 COMMUTATOR 引用了一个不存在的操作符时,系统会在系统目录中为该操作符建立一个占位项。这个占位项只有操作符名称、左右操作数类型和结果类型包含有效数据,因为这就是 PostgreSQL 此时能够推断出的全部信息。第一个操作符的目录项将链接到这个占位项。之后,当你定义第二个操作符时,系统会用第二个定义中的附加信息更新该占位项。如果在占位操作符补全之前尝试使用它,就只会得到一条错误消息。

31.13.2. NEGATOR

如果给出 NEGATOR 子句,它指定一个与正在定义的操作符 互为求反器的操作符。若操作符 A 和 B 都返回布尔结果,并且对所有可能的输 入 x、y,都有 (x A y) 等于 NOT (x B y),则称 A 是 B 的求反器。注意,B 也同样是 A 的求反器。例如,对大多数数据类型而言, < 和 >= 构成一对求反器。一个 操作符永远不可能合法地成为其自身的求反器。

与交换子不同,一对一元操作符完全可能合法地被标记为彼此的求反器;这意味 着对所有 x,都有 (A x) 等于 NOT (B x),或者对右一元操作符有等价的关系。

一个操作符的求反器必须与待定义操作符具有相同的左操作数类型和/或右操作数 类型,因此与 COMMUTATOR 一样,在 NEGATOR 子句中只需给出操作符名称即可。

提供求反器对查询优化器很有帮助,因为它允许把 NOT (x = y) 这样的表达式简化为 x <> y。这种情况比你想象得更常见,因为 NOT 操作可能会作为其他重排的结果被插入进来。

可以使用上面解释的定义交换子对的相同方法,来定义成对的求反器操作符。

31.13.3. RESTRICT

如果给出 RESTRICT 子句,它指定该操作符的限制选择率估 算函数。(注意,这里是函数名,而不是操作符名。) RESTRICT 子句只对返回 boolean 的二元操 作符有意义。限制选择率估算器的作用,是针对当前操作符和某个特定常量值, 猜测一张表中有多少比例的行会满足如下形式的 WHERE 子句条件:

column OP constant

这会帮助优化器大致了解具有这种形式的 WHERE 子句将淘 汰多少行。(你可能会问:如果常量在左边会怎样?嗯,这正是 COMMUTATOR 的作用之一……)

编写新的限制选择率估算函数远远超出了本章的范围,不过幸运的是,对于你自 己的很多操作符,通常都可以直接使用系统提供的某个标准估算器。标准的限制 选择率估算器如下:

eqsel 用于 =
neqsel 用于 <>
scalarltsel 用于 < 或 <=
scalargtsel 用于 > 或 >=

这些分类可能看起来有点奇怪,但仔细想想就会发现它们是有道理的。= 通常只会接受表中很小一部分行;<> 通常只会拒绝很小一部分行。< 接受的比例取决于给定常量落在该表列的值范围中的什么位置(恰好,这正是 ANALYZE 收集并提供给选择率估算器的信息)。对于相同的比较常量,<= 接受的比例会比 < 略大,但二者足够接近,不值得区分,尤其是无论如何我们很可能也只能做出粗略猜测。类似的说法也适用于 > 和 >=。

对于选择率非常高或非常低的操作符,即使它们实际上并不是真正的相等或不等 比较,你也常常可以勉强使用 eqsel 或 neqsel。例如,几何类型中的近似相等操作符就使用 eqsel,其依据是它们通常只会匹配表中很小一部分项。

对于那些能够以某种合理方式转换为数值标量、从而可进行范围比较的数据类 型,你可以使用 scalarltsel 和 scalargtsel。如果可能的话,请把该数据类型加入函数 convert_to_scalar()(位于 src/backend/utils/adt/selfuncs.c)所理解的范围 中。(最终,这个函数应被通过 pg_type 系统目录某一 列标识的、按数据类型划分的函数所取代;但这件事目前还没有发生。)如果不 这样做,系统仍然可以工作,但优化器的估算效果就不会像本可达到的那样好。

在 src/backend/utils/adt/geo_selfuncs.c 中,还为几 何操作符提供了其他选择率估算函数:areasel、 positionsel 和 contsel。截至 目前,这些函数都还只是桩实现,但你也许仍会想使用它们(或者更好的是,改 进它们)。

31.13.4. JOIN

如果给出 JOIN 子句,它指定该操作符的连接选择率估算函 数。(注意,这里是函数名,而不是操作符名。)JOIN 子 句只对返回 boolean 的二元操作符有意义。连接选择率估算器的 作用,是针对当前操作符,猜测两张表中有多少比例的行对会满足如下形式的 WHERE 子句条件:

table1.column1 OP table2.column2

与 RESTRICT 子句一样,这会极大地帮助优化器判断在若 干可能的连接顺序中,哪一种预计工作量最小。

与前面一样,本章不会尝试解释如何编写连接选择率估算函数,而只是建议你在 适用时使用某个标准估算器:

eqjoinsel 用于 =
neqjoinsel 用于 <>
scalarltjoinsel 用于 < 或 <=
scalargtjoinsel 用于 > 或 >=
areajoinsel 用于基于二维面积的比较
positionjoinsel 用于基于二维位置的比较
contjoinsel 用于基于二维包含关系的比较

31.13.5. HASHES

如果存在 HASHES 子句,它会告知系统:在基于该操作符 的连接中,可以使用哈希连接方法。HASHES 只对返回 boolean 的二元操作符有意义;在实践中,该操作符最好 是某种数据类型上的相等关系。

哈希连接背后的假设是:只有当左值和右值被哈希到同一个哈希码时,连接操作 符才有可能返回 true。如果两个值被放进不同的哈希桶,连接过程就根本不会 去比较它们,这实际上隐含假定连接操作符的结果必定为 false。因此,对于那 些并不表示相等关系的操作符,指定 HASHES 永远没有 意义。

若要被标记为 HASHES,该连接操作符必须出现在某个哈希 索引操作符类中。创建操作符时并不会强制这一点,因为那时引用它的操作符类 当然还不存在。但如果没有这样的操作符类存在,运行时尝试在哈希连接中使用 该操作符就会失败。系统需要通过该操作符类来查找与该操作符输入数据类型相 对应的、特定于数据类型的哈希函数。当然,在创建该操作符类之前,你还必须 先提供合适的哈希函数。

准备哈希函数时应格外小心,因为它有一些依赖机器的方式可能会导致结果不正 确。例如,如果你的数据类型是某种结构体,其中可能包含无意义的填充位,那 就不能简单地把整个结构体传给 hash_any。(除非你编写 了其他操作符和函数,以确保这些未使用位始终为零,而这正是推荐的策略。) 再比如,在符合 IEEE 浮点标准的机器上,负零和正零是 不同的值(位模式不同),但它们被定义为比较相等。如果某个浮点值可能包含 负零,就需要采取额外步骤,确保它生成与正零相同的哈希值。

注意

作为可哈希连接操作符底层实现的函数,必须标记为 immutable 或 stable。 如果它是 volatile,系统就永远不会尝试把该操作符用于哈希连接。

注意

如果某个可哈希连接操作符的底层函数被标记为 strict,那么该函数还必须是 完备的:也就是说,对于任意两个非空输入,它都应返回 true 或 false,而绝 不能返回 null。如果不遵循这条规则,对 IN 操作进行哈 希优化时可能会产生错误结果。(具体来说,IN 可能会在 按标准本应返回 null 的地方返回 false;或者抛出一条错误,抱怨它没有为 null 结果做好准备。)

31.13.6. MERGES (SORT1, SORT2, LTCMP, GTCMP)

如果存在 MERGES 子句,它会告知系统:在基于该操作符 的连接中,可以使用归并连接方法。MERGES 只对返回 boolean 的二元操作符有意义;在实践中,该操作符最好 是某种数据类型或某对数据类型上的相等关系。

归并连接的基本思想,是先把左表和右表分别排序,然后同步扫描它们。因此, 两种数据类型都必须能够被完全排序,而连接操作符必须只能在那对值位于排序 次序中“同一位置”时才成功。实际效果上,这意味着连接操作符 必须表现得像相等比较一样。但与哈希连接不同(哈希连接要求左右数据类型 最好相同,或者至少按位等价),只要两种不同的数据类型在逻辑上兼容,就 可以对它们进行归并连接。例如,smallint 与 integer 之间的相等操作符就是可归并连接的。我们只需要能把 两种数据类型都带入逻辑兼容排序序列的排序操作符即可。

归并连接的执行要求系统能够识别出与该归并连接相等操作符相关的四个操作 符:左操作数数据类型的小于比较、右操作数数据类型的小于比较、两种数据 类型之间的小于比较,以及两种数据类型之间的大于比较。(如果可归并连接 的操作符具有两个不同的操作数数据类型,这实际上是四个不同的操作符;但 当两个操作数类型相同时,三个小于操作符是同一个操作符。)可以通过名称 分别指定这些操作符,即分别使用 SORT1、SORT2、 LTCMP 和 GTCMP 选项。在指定 MERGES 时,如果省略了其中任何一个,系统将分别填入默认名称 <、<、<、>。 此外,如果这四个操作符选项中任何一个出现,就会认为隐含了 MERGES,因此可以只指定其中一部分,而让系统补全其余部分。

这四个比较操作符的操作数数据类型可以从可归并连接操作符的操作数类型推 断出来,因此与 COMMUTATOR 一样,这些子句中只需给出操作符 名称即可。除非你选用的操作符名称比较特殊,否则只需写 MERGES 并让系统补全细节就足够了。(与 COMMUTATOR 和 NEGATOR 一样,如果你碰巧先定义了 相等操作符,系统也能够建立占位操作符条目。)

对于你标记为可归并连接的操作符,还有一些附加限制。这些限制目前不会被 CREATE OPERATOR 检查,但如果其中任何一条不成立,在 使用该操作符时可能会出错:

  • 一个可归并连接的相等操作符必须拥有一个可归并连接的交换子(如果两个 操作数数据类型相同,则为它自身;如果不同,则应是相关的相等操作符)。

  • 如果存在一个可归并连接的操作符关联某两个数据类型 A 和 B,又存在另一 个可归并连接的操作符关联 B 和任何第三种数据类型 C,那么 A 和 C 也必须 有一个可归并连接的操作符;换句话说,可归并连接操作符的存在必须是可 传递的。

  • 如果你指定的四个比较操作符没有以兼容的方式对数据值排序,运行时会出 现奇怪的结果。

注意

作为可归并连接操作符底层实现的函数,必须标记为 immutable 或 stable。如 果它是 volatile,系统就永远不会尝试把该操作符用于归并连接。

注意

在 PostgreSQL 7.3 之前的版本中,没有 MERGES 这个简写形式:要创建一个可归并连接的操作符,必须显式 地同时写出 SORT1 和 SORT2。而且,当时也不存在 LTCMP 和 GTCMP 选项;那两个操作符的名称被硬编码 为 < 和 >。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。