支援版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援版本: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

36.15. 運算子最佳化資訊 #

PostgreSQL 運算子定義可以包含幾個選用子句,這些子句會告知系統關於運算子行為的實用資訊。 應在適當的時候提供這些子句,因為它們可以顯著提高使用該運算子的查詢的執行速度。 但是,如果您提供它們,則必須確保它們是正確的! 錯誤地使用最佳化子句可能會導致查詢速度變慢、輸出略有錯誤或其他糟糕的事情。 如果您不確定,可以隨時省略最佳化子句; 唯一的後果是查詢的執行速度可能會比需要的慢。

在未來的 PostgreSQL 版本中可能會新增其他最佳化子句。 這裡描述的子句都是 release 17.2 所理解的子句。

也可以將規劃器支援函數附加到作為運算子基礎的函數,從而提供另一種方式來告知系統關於運算子的行為。 有關更多資訊,請參閱第 36.11 節

36.15.1. COMMUTATOR #

COMMUTATOR 子句(如果提供)會命名一個運算子,該運算子是正在定義的運算子的交換子 (commutator)。 如果 (x A y) 等於 (y B x) 適用於所有可能的輸入值 x、y,我們就說運算子 A 是運算子 B 的交換子。 請注意,B 也是 A 的交換子。 例如,特定資料類型的運算子 <> 通常互為交換子,而運算子 + 通常與自身可交換。 但是運算子 - 通常與任何東西都不可交換。

可交換運算子的左運算元類型與其交換子的右運算元類型相同,反之亦然。 因此,PostgreSQL 只需要給定交換子運算子的名稱即可查找交換子,這也是 COMMUTATOR 子句中需要提供的全部內容。

對於將在索引和連接子句中使用的運算子來說,提供交換子資訊至關重要,因為這允許查詢最佳化器翻轉這樣的子句,以形成不同計畫類型所需的形式。 例如,考慮一個具有類似 tab1.x = tab2.y 的 WHERE 子句的查詢,其中 tab1.xtab2.y 屬於使用者定義的類型,並假設 tab2.y 已建立索引。 最佳化器無法產生索引掃描,除非它可以確定如何將子句翻轉為 tab2.y = tab1.x,因為索引掃描機制期望在給定的運算子的左側看到已建立索引的欄位。 PostgreSQL 不會僅僅假設這是一個有效的轉換 — = 運算子的建立者必須透過使用交換子資訊標記運算子來指定它是有效的。

36.15.2. 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 運算可能會因為其他重新排列而被插入。

36.15.3. RESTRICT #

如果提供 RESTRICT 子句,它會指定運算子的限制選擇性估計函數。(請注意,這是函數名稱,而不是運算子名稱。)RESTRICT 子句僅對回傳 boolean 的二元運算子有意義。限制選擇性估計器的想法是猜測表格中將滿足以下形式的 WHERE 子句條件的列的比例是多少:

column OP constant

對於當前運算子和特定的常數值。 這藉由讓最佳化器了解有多少列將被具有此形式的 WHERE 子句消除來輔助最佳化器。(您可能想知道,如果常數在左邊會發生什麼? 好吧,這就是 COMMUTATOR 的用途之一...)

編寫新的限制選擇性估計函數遠遠超出了本章的範圍,但幸運的是,對於您自己的許多運算子,您通常可以只使用系統的標準估計器之一。 這些是標準的限制估計器:

eqsel 適用於 =
neqsel 適用於 <>
scalarltsel 適用於 <
scalarlesel 適用於 <=
scalargtsel 適用於 >
scalargesel 適用於 >=

對於具有非常高或非常低選擇性的運算子,即使它們實際上不是相等或不相等,您也可以經常使用 eqselneqsel。 例如,近似相等的幾何運算子使用 eqsel,因為假設它們通常只會匹配表格中的一小部分條目。

您可以將 scalarltselscalarleselscalargtselscalargesel 用於可以透過某種合理方式轉換為數值純量以進行範圍比較的資料類型。 如果可能,請將資料類型新增到 src/backend/utils/adt/selfuncs.c 中的函數 convert_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。(除非您編寫其他運算子和函式以確保未使用的位元始終為零,這是建議的策略。)另一個例子是在符合IEEE浮點數標準的機器上,負零和正零是不同的值(不同的位元模式),但它們被定義為比較相等。如果浮點數值可能包含負零,則需要額外的步驟來確保它產生與正零相同的雜湊值。

可進行雜湊聯結的運算子必須具有交換子(如果兩個運算元資料型別相同,則為其本身,如果它們不同,則為相關的等值運算子),該交換子出現在相同的運算子族中。如果不是這種情況,則在使用該運算子時可能會發生規劃器錯誤。此外,對於支援多種資料型別的雜湊運算子族來說,為每種資料型別組合提供等值運算子是一個好主意(但並非嚴格要求);這可以實現更好的最佳化。

注意

可進行雜湊聯結的運算子所依據的函式必須標記為 immutable 或 stable。如果它是 volatile,系統將永遠不會嘗試將該運算子用於雜湊聯結。

注意

如果可進行雜湊聯結的運算子具有標記為 strict 的基礎函式,則該函式也必須是完整的:也就是說,對於任何兩個非空輸入,它應該返回 true 或 false,而不是 null。如果不遵守此規則,則 IN 運算的最佳化雜湊可能會產生錯誤的結果。(具體來說,IN 可能會返回 false,而根據標準,正確的答案應該是 null;或者它可能會產生錯誤,抱怨它沒有準備好處理 null 結果。)

36.15.6. MERGES #

如果存在 MERGES 子句,則告訴系統允許使用合併聯結方法來聯結基於此運算子的聯結。MERGES 僅對返回 boolean 的二元運算子有意義,並且在實踐中,該運算子必須表示某種資料型別或一對資料型別的相等性。

合併聯結基於將左側和右側表格排序到順序中,然後平行掃描它們的想法。因此,兩種資料型別都必須能夠完全排序,並且聯結運算子必須只能對在排序順序中的 相同位置 的值配對成功。在實踐中,這意味著聯結運算子的行為必須像相等性一樣。但是,只要它們在邏輯上相容,就可以合併聯結兩種不同的資料型別。例如,smallintinteger 相等的運算子是可合併聯結的。我們只需要排序運算子,它們將使兩種資料型別都進入邏輯上相容的序列。

若要標記為 MERGES,聯結運算子必須作為 btree 索引運算子族的等值成員出現。建立運算子時不會強制執行此操作,因為參考的運算子族當然還不存在。但是,除非可以找到匹配的運算子族,否則該運算子實際上不會用於合併聯結。MERGES 標記因此充當規劃器的提示,表明值得尋找匹配的運算子族。

可合併聯結的運算子必須具有交換子(如果兩個運算元資料型別相同,則為其本身,如果它們不同,則為相關的等值運算子),該交換子出現在相同的運算子族中。如果不是這種情況,則在使用該運算子時可能會發生規劃器錯誤。此外,對於支援多種資料型別的 btree 運算子族來說,為每種資料型別組合提供等值運算子是一個好主意(但並非嚴格要求);這可以實現更好的最佳化。

注意

可合併聯結的運算子所依據的函式必須標記為 immutable 或 stable。如果它是 volatile,系統將永遠不會嘗試將該運算子用於合併聯結。

提交更正

如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步澄清的地方,請使用 此表單 來報告文件問題。