Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

36.15. 操作符优化信息 #

36.15.1. COMMUTATOR
36.15.2. NEGATOR
36.15.3. RESTRICT
36.15.4. JOIN
36.15.5. HASHES
36.15.6. MERGES

一个 PostgreSQL 操作符定义可以包含多个可选子句,告知系统有关操作符行为的有用信息。应在适当的时候提供这些子句,因为它们可以极大地加快使用该操作符的查询执行速度。但是如果你提供了它们,你必须确保它们是正确的!优化子句使用不正确会导致查询速度慢、结果错误或其他不佳的情况。如果你不确定,你可以随时省略优化子句;其唯一的后果是查询可能比需要时运行得更慢。

PostgreSQL 的未来版本中可能会添加其他优化子句。这里描述的子句是 17.1 版本理解的所有子句。

还可以将规划器支持函数附加到作为操作符基础的函数,提供另一种方式来告知系统有关操作符行为的信息。有关详细信息,请参阅 第36.11节

36.15.1. COMMUTATOR #

如果提供了 COMMUTATOR 子句,则为正在定义的操作符命名一个交换律运算符。我们说运算符 A 是运算符 B 的交换律运算符,如果 (x A y) 等于 (y B x) 针对所有可能的输入值 x, y。请注意,B 也是 A 的交换律运算符。例如,特定数据类型的运算符 <> 通常是彼此的交换律运算符,而运算符 + 通常与它本身可交换。但运算符 - 通常无法与任何内容交换。

可交换运算符的左操作数类型与其交换律运算符的右操作数类型相同,反之亦然。因此 PostgreSQL 只需要交换律运算符的名称来查找交换律运算符,而这正是 COMMUTATOR 子句中需要提供的所有内容。

为将在索引和联合子句中使用的运算符提供换向信息至关重要,因为这允许查询优化器将这样的子句“翻转”为不同计划类型所需的表单。例如,考虑一个带有 WHERE 子句的查询,如 tab1.x = tab2.y,其中 tab1.xtab2.y 是用户定义的类型,假设将为 tab2.y 编制索引。优化器无法生成索引扫描,除非它能确定如何将子句翻转为 tab2.y = tab1.x,因为索引扫描机械装置期待在给定的运算符的左边看到已编制索引的列。PostgreSQL不会仅仅假定这是一个有效的转换——= 运算符的创建者必须通过使用换向信息标记该运算符来指定它有效。

36.15.2. NEGATOR #

如果提供了 NEGATOR 子句,则会命名一个运算符,该运算符是被定义运算符的否定运算符。如果两个运算符返回布尔结果,并且 (x A y) 等于 NOT (x B y),对于所有可能的输入 x、y,则我们说运算符 A 是运算符 B 的否定运算符。请注意,B 也是 A 的否定运算符。例如,<>= 是大多数数据类型的一对否定运算符。一个运算符永远不能有效地成为其自身的否定运算符。

不像换向操作符,一对一元运算符可以有效地标记为彼此的否定运算符;这意味着对于所有 x,(A x) 等于 NOT (B x)。

运算符的否定运算符必须具有与要定义的运算符相同的左和/或右操作数类型,因此,就像使用 COMMUTATOR 一样,只需要在 NEGATOR 子句中给定运算符名称。

提供否定运算符对查询优化器非常有帮助,因为它允许将诸如 NOT (x = y) 的表达式簡化为 x <> y。这种情况出现的频率比你想象的要高,因为 NOT 运算可以作为其他重排的结果插入。

36.15.3. RESTRICT #

如果提供了 RESTRICT 子句,则会命名一个运算符的限制选择性估计函数。(请注意,这是一个函数名称,而不是运算符名称。)RESTRICT 子句仅对返回 boolean 的二元运算符有意义。限制选择性估计器的理念是猜测表中哪些行的数量将满足形式为

column OP constant

对于当前运算符和特定常量值。这通过提示它有多少行将因具有此形式的 WHERE 子句而消除,来帮助优化程序。(您可能想知道,如果常量位于左侧,会发生什么?好,这是 COMMUTATOR 的用处之一...)

编写新的限制选择估计函数已经远远超出了本章的范围,但幸运的是,您通常只需为许多您自己的运算符使用一个系统的标准估计器即可。这些是标准限制估计器

eqsel 用于 =
neqsel 用于 <>
scalarltsel 用于 <
scalarlesel 用于 <=
scalargtsel 用于 >
scalargesel 用于 >=

即使它们不是真正的等式或不等式,您也经常可以使用 eqselneqsel 选择性非常高或非常低的运算符。例如,近似相等几何运算符使用 eqsel,前提是它们通常只匹配表中的很小一部分条目。

您可以将 scalarltselscalarleselscalargtselscalargesel 用于对数据类型进行比较,这些数据类型有一些合理的方法可以转换为数字标量以用于范围比较。如果可能,将数据类型添加到 src/backend/utils/adt/selfuncs.cconvert_to_scalar() 函数理解的数据类型中。(最终,此函数应替换为通过 pg_type 系统目录的列来标识的按数据类型划分的函数;但这尚未实现。)如果您不这样做,一切仍然可以正常工作,但优化程序的估计不会像其所能的那样好。

另一个有用的内置选择性估计函数是 matchingsel,如果为输入数据类型收集了标准 MCV 和/或直方图统计信息,它将适用于几乎所有二元运算符。其默认估计值设定为 eqsel 中使用的默认估计值的两倍,使其最适合比相等性稍微宽松的比较运算符。(或者您可以调用底层 generic_restriction_selectivity 函数,提供不同的默认估计值。)

针对 src/backend/utils/adt/geo_selfuncs.c 中的几何运算符设计了其他选择性估算函数:areaselpositionselcontsel。撰写时这些都只是存根,但你或许还是想使用它们(甚至对其进行改进)。

36.15.4. JOIN #

如果提供了 JOIN 子句,则会为运算符命名选择性估计联合函数。(请注意,这是一个函数名,而不是一个运算符名。)JOIN 子句仅针对返回 boolean 的二元运算符才有意义。选择性估算联合函数的思想是针对当前运算符,猜测一对表中哪一部分行将满足形式为 WHERE 的子句条件

table1.column1 OP table2.column2

RESTRICT 子句一样,这极大地帮助优化器确定哪几种可能的联接序列可能需要最少的执行工作。

与之前一样,本章不会尝试解释如何编写选择性估算联合函数,只建议你使用其中一个标准估算器(如果适用)

eqjoinsel 针对 =
neqjoinsel 针对 <>
scalarltjoinsel 针对 <
scalarlejoinsel 针对 <=
scalargtjoinsel 针对 >
scalargejoinsel 针对 >=
matchingjoinsel 针对通用匹配运算符
areajoinsel 针对基于 2D 区域的比较
positionjoinsel 针对基于 2D 位置的比较
contjoinsel 针对基于 2D 包含的比较

36.15.5. HASHES #

如果存在 HASHES 子句,则告诉系统可以对基于此运算符的联接使用哈希联接方法。HASHES 仅针对返回 boolean 的二元运算符有意义,而且在实践中运算符必须表示对某些数据类型或数据类型对的相等性。

哈希连接的底层假设是连接运算符只能对哈希到相同哈希代码的左右值对返回 true。如果两个值被放置到不同的哈希存储格中,连接运算符将绝不会比较它们,隐式假定连接运算符的结果必须为 false。因此,为不表示任何形式等式的运算符指定 HASHES 毫无意义。在大多数情况下,只支持对双边使用相同数据类型的运算符进行哈希处理才是实用的。但是,有时可以设计两个或更多数据类型的兼容哈希函数;即,甚至值有不同的表示形式,也会为相等值生成相同的哈希代码的函数。例如,在对不同宽度的整数进行哈希处理时,这种属性很容易安排。

要标记为 HASHES,连接运算符必须出现在哈希索引运算符系列中。在你创建运算符时未强制执行此项,因为引用运算符系列当然还并不存在。但是,如果没有该运算符系列,使用该运算符进行哈希连接的尝试将在运行时失败。系统需要运算符系列才能找到运算符输入数据类型的数据类型特定的哈希函数。当然,你必须在创建运算符系列之前创建合适的哈希函数。

准备一个哈希函数时应小心,因为存在它未能完成正确任务的机器依赖方式。例如,如果你的数据类型是结构中可能存在无意义填充位的结构,不能简单地将整个结构传递给 hash_any。(除非你编写其他运算符和函数以确保未使用的位始终为 0,这是建议的策略。) 另一个例子是,在符合IEEE浮点标准的机器上,负零与正零是不同的值(不同的位模式),但被定义为进行相等比较。如果浮点值可能包含负零,则需要采取额外的步骤来确保它生成与正零相同哈希值。

可哈希连接的运算符必须具有换向器(如果两个操作数数据类型相同,则为它自身,如果不同,则为相关等式运算符),并且出现在相同的运算符系列中。如果不是这种情况,当运算符被使用时,可能会出现规划程序错误。此外,为支持多种数据类型的哈希运算符系列提供每种数据类型组合的等式运算符(尽管未严格要求),是个好主意;这能改进优化。

注意

哈希可连接运算符的底层函数必须标记为不可变或稳定的。如果它是易失性的,则系统将从不尝试将此运算符用于哈希联接。

注意

如果哈希可连接运算符具有标记为严格的底层函数,则该函数还必须是完整的:也就是说,对于任何两个非空输入,都应返回真或假,永不返回空值。如果不遵循此规则,则 IN 操作的哈希优化可能会产生错误的结果。(具体而言, IN 可能会在标准正确的答案应为空值的位置返回 false;或者,它可能会返回错误,抱怨它未针对空值结果而准备。)

36.15.6. MERGES #

如果存在 MERGES 子句,它将告知系统可以对基于此运算符的联接使用合并联接方法。 MERGES 仅适用于返回 boolean 的二元运算符,并且在实践中,运算符必须表示某些数据类型或数据类型对的相等性。

合并联接基于对左表和右表排序并随后并行扫描它们的理念。因此,两个数据类型都必须完全可排序,并且联接运算符必须仅对排序顺序中位于“相同位置”的值对起作用的运算符。实际上,这意味着联接运算符必须表现得像相等性。但是,只要逻辑上兼容,便可以合并联接两个不同的数据类型。例如, smallint -vs- integer 等价性运算符是可合并联接的。我们只需要将两个数据类型都纳入逻辑兼容序列的排序运算符即可。

要标记为 MERGES,联接运算符必须显示为 btree 索引运算符系列的相等成员。在创建运算符时不会强制执行此操作,因为引用运算符系列当然尚未存在。但是,只有在找到匹配运算符系列的情况下,运算符才实际用于合并联接。因此, MERGES 标志充当一个提示,告知计划程序值得寻找匹配的运算符系列。

一个可合并 join 的运算符必须有一个交换器(如果两个操作数数据类型相同,则为其本身,或者如果它们不同,则为相关的等值运算符),该交换器出现在同一运算符组中。如果没有出现这种情况,在使用该运算符时可能会出现规划错误。此外,对支持多种数据类型的 btree 运算符族提供每种数据类型组合的等值运算符也是一个好主意(但不是严格要求的);这样做可以更好地进行优化。

注意

一个可合并 join 的运算符基础函数必须标记为不可变的或稳定的。如果它是不稳定的,系统将永远不会尝试对该运算符使用合并 join。