# 38.15.

运营商优化信息38.15.1.

换向器38.15.2.

否定者38.15.3.

严格38.15.4.

加入38.15.5.

哈希38.15.6.

合并一个 PostgreSQL 运算符定义可以包含几个可选子句,这些子句告诉系统有关运算符行为方式的有用信息。应在适当时提供这些子句,因为它们可以显着加快使用运算符的查询的执行速度。但是,如果您提供它们,则必须确保它们是正确的!不正确地使用优化子句可能会导致查询缓慢、输出稍有错误或其他坏事。如果您不确定,可以随时省略优化子句;

唯一的后果是查询可能运行得比他们需要的慢。在 PostgreSQL 的未来版本中可能会添加额外的优化子句。

这里描述的都是14.2版本能理解的。还可以将规划器支持功能附加到作为操作员基础的功能,提供另一种告诉系统操作员行为的方式。第 38.11 节

# 了解更多信息。38.15.1.

换向器换向器子句,如果提供的话,命名一个运算符,它是被定义的运算符的交换子。如果 (x A y) 对于所有可能的输入值 x, y 等于 (y B x),我们说算子 A 是算子 B 的交换子。请注意,B 也是 A 的交换子。例如,运算符<>对于特定的数据类型,通常是彼此的交换器,而运算符+通常与自身可交换。但运营商-通常不与任何事物可交换。

可交换运算符的左操作数类型与其交换子的右操作数类型相同,反之亦然。所以 commutator 运算符的名称是 PostgreSQL 查找 commutator 所需的全部内容,这就是需要在换向器条款。

为将在索引和连接子句中使用的运算符提供交换器信息至关重要,因为这允许查询优化器将此类子句“翻转”为不同计划类型所需的形式。例如,考虑一个带有 WHERE 子句的查询,例如tab1.x = tab2.y, 在哪里选项卡1.xtab2.y是用户定义的类型,并假设tab2.y被索引。优化器无法生成索引扫描,除非它可以确定如何将子句翻转到tab2.y = tab1.x,因为索引扫描机器希望看到给定运算符左侧的索引列。PostgreSQL 将不是简单地假设这是一个有效的转换——=运算符必须通过用换向器信息标记运算符来指定它是有效的。

当您定义一个自交换运算符时,您只需执行此操作。当您定义一对交换运算符时,事情会有些棘手:要定义的第一个运算符如何引用您尚未定义的另一个运算符?这个问题有两种解决方案:

  • 一种方法是省略换向器您定义的第一个运算符中的子句,然后在第二个运算符的定义中提供一个。由于 PostgreSQL 知道交换运算符是成对出现的,所以当它看到第二个定义时,它会自动返回并填写缺失的换向器第一个定义中的子句。

  • 另一种更直接的方法是包括换向器两种定义中的子句。当PostgreSQL处理第一个定义并意识到换向器指不存在的运算符,系统将在系统目录中为该运算符创建一个虚拟条目。这个伪条目将只包含运算符名称、左操作数类型和右操作数类型以及结果类型的有效数据,因为这是PostgreSQL此时可以推断的全部内容。第一个操作员的目录条目将链接到此虚拟条目。稍后,当您定义第二个运算符时,系统将使用第二个定义中的附加信息更新虚拟条目。如果在填充虚拟运算符之前尝试使用它,只会收到一条错误消息。

# 38.15.2.否定词

这个否定词子句(如果提供)指定一个运算符,该运算符是所定义运算符的否定符。如果对于所有可能的输入x,y,都返回布尔结果且(x A y)不等于(x B y),那么我们说算子A是算子B的求反子。注意,B也是A的求反子。例如,<>=对于大多数数据类型,都是一对否定符。运算符永远不能有效地作为自己的否定符。

与交换子不同,一对一元运算符可以有效地标记为彼此的否定子;这意味着(ax)不等于(bx)所有x。

运算符的求反器必须与要定义的运算符具有相同的左操作数类型和/或右操作数类型,就像换向器,只需在否定词条款

提供否定符对查询优化器非常有帮助,因为它允许以下表达式不是(x=y)简化为x<>y.这比你想象的要频繁,因为操作可以作为其他重新排列的结果插入。

对于换向器对,可以使用上面解释的相同方法来定义反算符对。

# 38.15.3.限制

这个限制子句(如果提供)为运算符命名一个限制选择性估计函数。(请注意,这是一个函数名,而不是运算符名。)限制子句只对返回布尔值限制选择性估计器背后的想法是猜测表中的行的哪一部分将满足哪里-表格的条款条件:

column OP constant

对于当前运算符和特定的常量值。这有助于优化器,让它了解到通过哪里具有此形式的子句。(如果常数在左边,你可能想知道会发生什么?这就是换向器是为了……)

编写新的限制选择性估计函数远远超出了本章的范围,但幸运的是,您通常可以对自己的许多运算符使用系统的标准估计值之一。以下是标准限制估计值:

eqsel对于=
内克塞尔对于<>
scalarltsel对于<
scalarlesel对于<=
scalargtsel对于>
斯卡拉杰尔对于>=

你可以经常使用这两种方法中的任何一种eqsel内克塞尔对于选择性非常高或非常低的运算符,即使它们不是真正的相等或不相等。例如,几何运算符使用的近似等式eqsel假设它们通常只匹配表中的一小部分条目。

你可以用scalarltsel, scalarlesel, scalargtsel斯卡拉杰尔用于对数据类型进行比较,这些数据类型有一些合理的方法可以转换为数值标量进行范围比较。如果可能,将数据类型添加到函数所理解的数据类型中将_转换为_标量()在里面src/backend/utils/adt/selfuncs。C(最终,该函数应替换为通过pg_型系统目录;但这还没有发生。)如果不这样做,事情仍会继续,但优化器的估计值不会像预期的那样好。

另一个有用的内置选择性估计函数是配对赛,如果为输入数据类型收集标准MCV和/或直方图统计信息,则几乎任何二进制运算符都适用。其默认估计值设置为中使用的默认估计值的两倍eqsel,使其最适合于比等式更严格的比较运算符。(或者你可以称之为潜在的通用限制选择性函数,提供不同的默认估计。)

还有一些额外的选择性估计函数是为几何算子设计的src/backend/utils/adt/geo_selfuncs。C: 阿雷塞尔, 位置选择控制.在本文中,这些只是存根,但无论如何,你可能想要使用它们(或者更好,改进它们)。

# 38.15.4. 参加

这个参加子句(如果提供)为运算符命名连接选择性估计函数。(请注意,这是一个函数名,而不是运算符名。)参加子句只对返回布尔值连接选择性估计器背后的想法是猜测一对表中的行中有多少部分满足哪里-表格的条款条件:

table1.column1 OP table2.column2

对于当前操作员。就跟限制子句中,这对优化器有很大的帮助,因为它可以找出几个可能的连接序列中哪一个可能花费最少的工作。

与之前一样,本章不会试图解释如何编写连接选择性估计器函数,但只建议您使用标准估计器之一(如果适用):

eqjoinsel对于=
奈克乔伊塞尔对于<>
scalarltjoinsel对于<
scalarlejoinsel对于<=
scalargtjoinsel对于>
鳞片对于>=
匹配Joinsel对于泛型匹配运算符
areajoinsel用于基于二维面积的比较
位置Joinsel用于基于二维位置的比较
康乔伊塞尔用于基于二维包容的比较

# 38.15.5. 散列

这个散列子句(如果存在)告诉系统允许基于此运算符对联接使用哈希联接方法。散列只对返回布尔值,在实践中,运算符必须表示某些数据类型或一对数据类型的相等性。

hash-join的基本假设是,join运算符只能对散列到同一哈希代码的左右值对返回true。如果两个值放在不同的散列桶中,则联接将永远不会对它们进行比较,隐式地假设联接运算符的结果必须为false。因此,具体说明是没有意义的散列对于不表示某种形式的等式的运算符。在大多数情况下,只支持两边都采用相同数据类型的运算符的哈希。然而,有时可以为两种或两种以上的数据类型设计兼容的哈希函数;也就是说,函数将为“相等”值生成相同的哈希代码,即使这些值具有不同的表示形式。例如,当散列不同宽度的整数时,排列这个属性相当简单。

标记散列,联接运算符必须出现在哈希索引运算符族中。创建操作符时不会强制执行此操作,因为引用操作符族当然还不存在。但是,如果不存在这样的运算符族,尝试在哈希联接中使用该运算符将在运行时失败。系统需要运算符族为运算符的输入数据类型查找特定于数据类型的哈希函数。当然,在创建运算符族之前,还必须创建合适的哈希函数。

在准备散列函数时应该小心,因为有一些依赖于机器的方式可能会导致它做不到正确的事情。例如,如果您的数据类型是一个结构,其中可能有一些不感兴趣的pad位,那么您不能简单地将整个结构传递给有吗(除非编写其他运算符和函数以确保未使用的位始终为零,这是推荐的策略。)另一个例子是,在满足IEEE浮点标准的机器上,负零和正零是不同的值(不同的位模式),但它们被定义为比较相等。如果浮点值可能包含负零,则需要额外的步骤来确保它生成与正零相同的哈希值。

哈希可接合运算符必须具有出现在同一运算符族中的换向器(如果两个操作数数据类型相同,则换向器本身;如果两个操作数数据类型不同,则换向器具有相关的相等运算符)。如果情况并非如此,则在使用运算符时可能会出现规划器错误。此外,对于支持多种数据类型的哈希运算符族来说,为每种数据类型的组合提供相等运算符也是一个好主意(但不是严格要求);这允许更好的优化。

# 笔记

哈希可连接运算符的基础函数必须标记为不可变或稳定。如果它是易变的,系统将永远不会尝试使用该运算符进行哈希联接。

# 笔记

如果hash joinable运算符的基础函数被标记为strict,那么该函数也必须是完整的:也就是说,对于任意两个非null输入,它应该返回true或false,而不是null。如果不遵循此规则,则在里面操作可能会产生错误的结果。(具体而言,在里面如果根据标准正确答案为空,则可能返回false;或者它可能会产生一个错误,抱怨它没有为空结果做好准备。)

# 38.15.6. 合并

这个合并子句(如果存在)告诉系统允许基于此运算符对联接使用合并联接方法。合并只对返回布尔值,在实践中,运算符必须表示某些数据类型或一对数据类型的相等性。

Merge join基于将左侧和右侧的表按顺序排序,然后并行扫描它们的思想。因此,这两种数据类型都必须能够进行完全排序,并且联接运算符必须只能对排序顺序中位于“同一位置”的值对进行成功排序。实际上,这意味着联接运算符的行为必须类似于等式。但是,只要两种不同的数据类型在逻辑上是兼容的,就可以合并并连接它们。例如短整型-对抗-整数相等运算符是合并可接合的。我们只需要将两种数据类型带入逻辑兼容序列的排序运算符。

标记合并,联接运算符必须显示为B树索引运算符族。创建操作符时不会强制执行此操作,因为引用操作符族当然还不存在。但除非能找到匹配的运算符族,否则该运算符实际上不会用于合并联接。这个合并因此,标志向规划者暗示,值得寻找匹配的运算符族。

合并可接合运算符必须具有出现在同一运算符族中的换向器(如果两个操作数数据类型相同,则换向器本身;如果两个操作数数据类型不同,则换向器具有相关的相等运算符)。如果情况并非如此,则在使用运算符时可能会出现规划器错误。此外,这是一个好主意(但不是严格要求)B树运算符族,支持多种数据类型,为每种数据类型的组合提供相等运算符;这允许更好的优化。

# 笔记

merge joinable运算符的基础函数必须标记为immutable或stable。如果它是易变的,系统将永远不会尝试使用该运算符进行合并联接。