PostgreSQL 操作符定义可以包含几个可选的子句,这些子句告诉系统关于操作符行为的有用的信息。只要合适,就应该提供这些子句,因为它们可以显著提高使用该操作符的查询的执行速度。但是,如果提供这些子句,则必须确保它们是正确的!不正确地使用优化子句可能会导致查询速度缓慢、输出微妙错误或其他糟糕的事情。如果您不确定某个优化子句,可以始终将其省略;唯一的后果是查询的运行速度可能比需要的慢。
未来的 PostgreSQL 版本可能会添加额外的优化子句。这里描述的子句是 17.2 版本理解的所有子句。
还可以将规划器支持函数附加到操作符的基础函数,从而提供另一种方式来告诉系统关于操作符的行为。有关更多信息,请参见第 36.11 节。
COMMUTATOR
#如果提供了 COMMUTATOR
子句,则它会命名一个操作符,该操作符是正在定义的操作符的交换符。如果对于所有可能的输入值 x、y,(x A y) 等于 (y B x),则我们说操作符 A 是操作符 B 的交换符。请注意,B 也是 A 的交换符。例如,特定数据类型的操作符 <
和 >
通常是彼此的交换符,并且操作符 +
通常与自身可交换。但是,操作符 -
通常与任何东西都不可交换。
可交换的操作符的左操作数类型与其交换符的右操作数类型相同,反之亦然。因此,交换符操作符的名称是 PostgreSQL 需要查找交换符的全部内容,并且这是需要在 COMMUTATOR
子句中提供的全部内容。
为将在索引和连接子句中使用的操作符提供交换符信息至关重要,因为这允许查询优化器将此类子句“翻转过来”,以适应不同计划类型所需的形式。例如,考虑一个带有类似 tab1.x = tab2.y
的 WHERE 子句的查询,其中 tab1.x
和 tab2.y
是用户定义类型,并假设 tab2.y
已索引。优化器无法生成索引扫描,除非它可以确定如何将子句翻转为 tab2.y = tab1.x
,因为索引扫描机制希望看到它给定的操作符的左侧索引列。PostgreSQL 将不会简单地假设这是一个有效的转换 — =
操作符的创建者必须通过使用交换符信息标记操作符来指定它是有效的。
NEGATOR
#如果提供了 NEGATOR
子句,则它会命名一个操作符,该操作符是正在定义的操作符的否定符。如果两个操作符都返回布尔结果,并且对于所有可能的输入 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
操作可能会作为其他重新排列的结果而插入。
RESTRICT
#如果提供了 RESTRICT
子句,则它会命名操作符的限制选择性估计函数。(请注意,这是一个函数名称,而不是操作符名称。)RESTRICT
子句仅对返回 boolean
的二元操作符有意义。限制选择性估计器的背后思想是猜测表中满足形式为
column OP constant
的 WHERE
子句条件(针对当前操作符和特定常量值)的行数比例。这通过给出关于这种形式的 WHERE
子句将消除多少行的概念来帮助优化器。(如果您想知道常量在左侧会发生什么?嗯,这是 COMMUTATOR
的用途之一...)
编写新的限制选择性估计函数远远超出了本章的范围,但幸运的是,通常您可以为自己的许多操作符使用系统的一个标准估计器。这些是标准限制估计器
eqsel 用于 = |
neqsel 用于 <> |
scalarltsel 用于 < |
scalarlesel 用于 <= |
scalargtsel 用于 > |
scalargesel 用于 >= |
对于具有非常高或非常低的选择性的操作符,即使它们实际上不是相等或不相等,您也可以经常使用 eqsel
或 neqsel
。例如,近似相等的几何操作符使用 eqsel
,假设它们通常只匹配表中一小部分条目。
您可以将 scalarltsel
、scalarlesel
、scalargtsel
和 scalargesel
用于具有将转换为数值标量以进行范围比较的合理方式的数据类型的比较。如果可能,请将数据类型添加到 src/backend/utils/adt/selfuncs.c
中的函数 convert_to_scalar()
所理解的数据类型中。(最终,此函数应替换为通过 pg_type
系统目录的列标识的每个数据类型函数;但这尚未发生。)如果不这样做,事情仍然可以工作,但优化器的估计值将不如它们可以达到的好。
另一个有用的内置选择性估计函数是 matchingsel
,如果为输入数据类型收集了标准的 MCV 和/或直方图统计信息,它几乎适用于任何二元运算符。它的默认估计值设置为 eqsel
中使用的默认估计值的两倍,使其最适合那些比相等性稍弱的比较运算符。(或者,您可以调用底层的 generic_restriction_selectivity
函数,提供不同的默认估计值。)
在 src/backend/utils/adt/geo_selfuncs.c
中,有为几何运算符设计的其他选择性估计函数:areasel
、positionsel
和 contsel
。在撰写本文时,这些只是存根,但您可能仍然想使用它们(或者更好的是,改进它们)。
JOIN
#如果提供了 JOIN
子句,它会指定运算符的连接选择性估计函数。(请注意,这是一个函数名,而不是运算符名。)JOIN
子句仅对返回 boolean
的二元运算符有意义。连接选择性估计器的目的是猜测一对表中满足以下形式的 WHERE
子句条件的行所占的比例:
table1.column1 OP table2.column2
对于当前运算符。与 RESTRICT
子句一样,这通过让优化器找出几种可能的连接顺序中哪一种可能需要最少的工作,从而极大地帮助了优化器。
和之前一样,本章不会尝试解释如何编写连接选择性估计器函数,而只是建议您在适用时使用标准估计器之一
eqjoinsel 用于 = |
neqjoinsel 用于 <> |
scalarltjoinsel 用于 < |
scalarlejoinsel 用于 <= |
scalargtjoinsel 用于 > |
scalargejoinsel 用于 >= |
matchingjoinsel 用于通用匹配运算符 |
areajoinsel 用于基于 2D 区域的比较 |
positionjoinsel 用于基于 2D 位置的比较 |
contjoinsel 用于基于 2D 包含的比较 |
HASHES
#如果存在 HASHES
子句,则它告诉系统可以对基于此运算符的连接使用哈希连接方法。HASHES
仅对返回 boolean
的二元运算符有意义,并且实际上运算符必须表示某种数据类型或一对数据类型的相等性。
哈希连接的底层假设是,只有当左右值对哈希到相同的哈希码时,连接运算符才会返回 true。如果两个值放入不同的哈希桶中,则连接永远不会比较它们,隐含地假设连接运算符的结果必须为 false。因此,为不表示某种形式的相等性的运算符指定 HASHES
是没有意义的。在大多数情况下,仅对两侧采用相同数据类型的运算符支持哈希才是实用的。但是,有时可以为两种或多种数据类型设计兼容的哈希函数;也就是说,对于“相等”的值,即使这些值具有不同的表示形式,也会生成相同的哈希码的函数。例如,当哈希不同宽度的整数时,很容易安排此属性。
要标记为 HASHES
,连接运算符必须出现在哈希索引运算符族中。在创建运算符时,不会强制执行此操作,因为当然,引用的运算符族可能还不存在。但是,如果在运行时没有这样的运算符族,则尝试在哈希连接中使用该运算符将失败。系统需要运算符族来查找运算符的输入数据类型的特定于数据类型的哈希函数。
在准备哈希函数时应谨慎,因为在某些依赖机器的方式中,它可能无法执行正确的操作。例如,如果您的数据类型是一个结构,其中可能存在不感兴趣的填充位,则不能简单地将整个结构传递给 hash_any
。(除非您编写其他运算符和函数来确保未使用的位始终为零,这是建议的策略。)另一个示例是,在满足以下条件的机器上IEEE浮点标准,负零和正零是不同的值(不同的位模式),但它们被定义为比较相等。如果浮点值可能包含负零,则需要额外的步骤来确保它生成与正零相同的哈希值。
可哈希连接的运算符必须具有出现在同一运算符族中的交换子(如果两个操作数数据类型相同,则为自身,如果它们不同,则为相关的相等运算符)。如果不是这种情况,则在使用运算符时可能会发生规划器错误。此外,支持多种数据类型的哈希运算符族最好(但不是严格要求)为数据类型的每个组合提供相等运算符;这允许更好的优化。
可哈希连接运算符的基础函数必须标记为 immutable 或 stable。如果它是 volatile,则系统将永远不会尝试使用该运算符进行哈希连接。
如果可哈希连接的运算符的基础函数标记为 strict,则该函数也必须是 complete 的:也就是说,对于任何两个非 null 输入,它应返回 true 或 false,而绝不返回 null。如果未遵循此规则,则对 IN
操作的哈希优化可能会产生错误的结果。(具体而言,IN
可能会返回 false,而根据标准正确答案应为 null;或者它可能会产生错误,抱怨它没有为 null 结果做好准备。)
MERGES
#如果存在 MERGES
子句,则它告诉系统可以对基于此运算符的连接使用合并连接方法。MERGES
仅对返回 boolean
的二元运算符有意义,并且实际上运算符必须表示某种数据类型或一对数据类型的相等性。
合并连接基于将左表和右表排序,然后并行扫描它们的想法。因此,两种数据类型都必须能够完全排序,并且连接运算符必须是一种只能在排序顺序中位于“同一位置”的值对上成功的方法。实际上,这意味着连接运算符的行为必须类似于相等。但是,可以合并连接两种不同的数据类型,只要它们在逻辑上兼容即可。例如,smallint
-与-integer
相等运算符是可合并连接的。我们只需要排序运算符,将两种数据类型都带入逻辑上兼容的序列中。
要标记为 MERGES
,连接运算符必须作为 btree
索引运算符族的相等成员出现。在创建运算符时,不会强制执行此操作,因为当然,引用的运算符族可能还不存在。但是,除非可以找到匹配的运算符族,否则该运算符实际上不会用于合并连接。MERGES
标志因此充当规划器的提示,表示值得查找匹配的运算符族。
可合并连接的运算符必须具有出现在同一运算符族中的交换子(如果两个操作数数据类型相同,则为自身,如果它们不同,则为相关的相等运算符)。如果不是这种情况,则在使用运算符时可能会发生规划器错误。此外,对于支持多种数据类型的 btree
运算符族,最好(但不是严格要求)为数据类型的每个组合提供相等运算符;这允许更好的优化。
可合并连接的运算符的基础函数必须标记为 immutable 或 stable。如果它是 volatile,则系统将永远不会尝试使用该运算符进行合并连接。
如果您在文档中看到任何不正确、与特定功能不符或需要进一步澄清的内容,请使用此表单报告文档问题。