一個 PostgreSQL 運算子定義可以包含幾個可選的子句,用於告知系統有關運算子行為的有用資訊。只要合適,就應該提供這些子句,因為它們可以極大地加速使用該運算子的查詢的執行。但是,如果您提供它們,您必須確保它們是正確的!錯誤地使用最佳化子句可能導致查詢緩慢、輸出結果微妙錯誤或其他不良後果。如果您不確定,可以隨時省略最佳化子句;唯一的後果是查詢可能執行得比它們本應慢。
將來版本的 PostgreSQL 可能會新增額外的最佳化子句。這裡描述的子句是版本 18.0 理解的所有子句。
還可以將查詢規劃器支援函式附加到運算子的底層函式上,這提供了另一種告知系統運算子行為的方法。有關更多資訊,請參閱 第 36.11 節。
COMMUTATOR
#如果提供了 COMMUTATOR
子句,則它指定一個運算子,該運算子是被定義運算子的交換運算元。我們說運算子 A 是運算子 B 的交換運算元,如果對於所有可能的輸入值 x, y,都有 (x A y) 等於 (y B x)。請注意,B 也是 A 的交換運算元。例如,對於特定資料型別,運算子 <
和 >
通常是彼此的交換運算元,而運算子 +
通常與自身是可交換的。但是運算子 -
通常與任何運算子都不可交換。
可交換運算子的左運算元型別與其交換運算元的右運算元型別相同,反之亦然。因此,PostgreSQL 所需的交換運算元名稱就是查詢該交換運算元所需的所有資訊,這也是 COMMUTATOR
子句中需要提供的全部資訊。
為將在索引和連線子句中使用的運算子提供交換運算元資訊至關重要,因為這允許查詢最佳化器將這樣的子句“翻轉”以適應不同計劃型別所需的格式。例如,考慮一個帶有 WHERE 子句的查詢,如 tab1.x = tab2.y
,其中 tab1.x
和 tab2.y
是使用者定義的型別,並且假設 tab2.y
被索引。最佳化器無法生成索引掃描,除非它能夠確定如何將子句翻轉為 tab2.y = tab1.x
,因為索引掃描機制期望在給定的運算子的左側看到被索引的列。PostgreSQL **不會**簡單地假定這是一個有效的轉換 — =
運算子的建立者必須透過標記該運算子的交換運算元資訊來指定它是有效的。
NEGATOR
#如果提供了 NEGATOR
子句,則它指定一個運算子,該運算子是被定義運算子的否定運算元。我們說運算子 A 是運算子 B 的否定運算元,如果兩個運算子都返回布林結果,並且對於所有可能的輸入 x, y,都有 (x A y) 等於 NOT (x B y)。請注意,B 也是 A 的否定運算元。例如,對於大多數資料型別,<
和 >=
是一對否定運算元。一個運算子永遠不能合法地是它自己的否定運算元。
與交換運算元不同,一對一元運算子可以合法地被標記為彼此的否定運算元;這意味著對於所有 x,都有 (A x) 等於 NOT (B x)。
運算子的否定運算元必須與要定義的運算子具有相同的左和/或右運算元型別,因此與 COMMUTATOR
相同,在 NEGATOR
子句中只需要給出運算子名稱。
提供否定運算元對查詢最佳化器非常有幫助,因為它允許將 NOT (x = y)
這樣的表示式簡化為 x <> y
。這種情況出現的頻率比您想象的要高,因為 NOT
操作可能會作為其他重排的後果而被插入。
RESTRICT
#如果提供了 RESTRICT
子句,則它指定運算子的限制選擇性估計函式。(請注意,這是一個函式名稱,而不是運算子名稱。) RESTRICT
子句僅對返回 boolean
的二元運算子有意義。限制選擇性估計器的想法是猜測給定運算子和特定常量值時,表中滿足 WHERE
子句的行所佔的比例。
column OP constant
(您可能會想,如果常量在左邊怎麼辦?嗯,這就是 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。如果它是易變的,系統將永遠不會嘗試將該運算子用於雜湊連線。
如果可雜湊連線的運算子具有一個標記為 strict 的底層函式,那麼該函式還必須是 complete:也就是說,它應該為任何兩個非空輸入返回 true 或 false,絕不返回 null。如果不遵守此規則,則雜湊最佳化 IN
操作可能會產生錯誤的結果。(具體來說,IN
可能會返回 false,而根據標準,正確答案應為 null;或者它可能會產生一個錯誤,抱怨它沒有準備好處理 null 結果。)
MERGES
#如果存在 MERGES
子句,則它告知系統,可以使用合併連線方法來基於此運算子進行連線。MERGES
僅對返回 boolean
的二元運算子有意義,並且實際上該運算子必須代表某種資料型別或資料型別對的相等性。
合併連線基於這樣一個思想:對左表和右表進行排序,然後並行掃描它們。因此,兩種資料型別都必須能夠被完全排序,並且連線運算子必須是隻能在排序順序中處於“相同位置”的值對上成功的運算子。實際上,這意味著連線運算子的行為必須類似於相等性。但是,只要邏輯上相容,就可以合併連線兩種不同的資料型別。例如,smallint
與 integer
的相等運算子是可合併連線的。我們只需要排序運算子能夠將兩種資料型別帶入邏輯相容的序列。
要被標記為 MERGES
,連線運算子必須作為一個相等成員出現在 btree
索引運算子族中。在建立運算子時,這不會被強制執行,因為引用運算子族當然還不存在。但是,除非找到匹配的運算子族,否則該運算子實際上不會用於合併連線。MERGES
標誌因此充當規劃器的提示,表明值得查詢匹配的運算子族。
可合併連線的運算子必須具有一個交換運算元(如果兩個運算元資料型別相同,則為自身;如果不同,則為相關的相等運算子),該交換運算元出現在同一個運算子族中。如果不是這種情況,則在使用運算子時可能會發生規劃器錯誤。此外,對於支援多種資料型別的 btree
運算子族,提供每種資料型別組合的相等運算子是一個好主意(但並非嚴格要求);這可以實現更好的最佳化。
可合併連線的運算子的底層函式必須被標記為 immutable 或 stable。如果它是易變的,系統將永遠不會嘗試將該運算子用於合併連線。
如果您在文件中看到任何不正確的內容,與您對特定功能的使用經驗不符,或者需要進一步澄清,請使用 此表單 報告文件問題。