支援版本:目前 (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 / 7.1

36.16. 索引擴充介面 #

到目前為止,描述的程序可讓您定義新的類型、新的函數和新的運算子。但是,我們還不能在新資料類型的欄位上定義索引。為此,我們必須為新的資料類型定義運算子類別。在本節的後面,我們將在一個範例中說明這個概念:B 樹索引方法的一個新的運算子類別,它以遞增的絕對值順序儲存和排序複數。

運算子類別可以分組為運算子家族,以顯示在語意上相容的類別之間的關係。當只涉及單一資料類型時,運算子類別就足夠了,所以我們將首先關注這種情況,然後再回到運算子家族。

36.16.1. 索引方法和運算子類別 #

運算子類別與索引存取方法相關聯,例如B 樹GIN。可以使用CREATE ACCESS METHOD定義自訂索引存取方法。有關詳細資訊,請參閱第 62 章

索引方法的常式不直接了解索引方法將要操作的資料類型。相反,運算子類別識別索引方法需要使用的一組操作,以便處理特定的資料類型。運算子類別之所以如此命名,是因為它們指定的一件事是可以與索引一起使用的WHERE子句運算子集合(也就是可以轉換為索引掃描條件)。運算子類別還可以指定一些支援函數,這些函數是索引方法的內部操作所需要的,但不直接對應於可以與索引一起使用的任何WHERE子句運算子。

可以為相同的資料類型和索引方法定義多個運算子類別。透過這樣做,可以為單一資料類型定義多組索引語意。例如,B 樹索引需要為它所操作的每個資料類型定義排序順序。對於複數資料類型來說,擁有一個按複數絕對值排序的 B 樹運算子類別、另一個按實部排序的 B 樹運算子類別等等可能是有用的。通常,其中一個運算子類別將被認為是最常用的,並將被標記為該資料類型和索引方法的預設運算子類別。

相同的運算子類別名稱可以用於多種不同的索引方法(例如,B 樹和雜湊索引方法都有名為int4_ops的運算子類別),但每個這樣的類別都是一個獨立的實體,必須單獨定義。

36.16.2. 索引方法策略 #

與運算子類別相關聯的運算子由策略編號識別,這些編號用於識別每個運算子在其運算子類別上下文中的語意。例如,B 樹對鍵施加嚴格的排序,從較小到較大,因此像小於大於或等於這樣的運算子對於 B 樹來說是有意義的。由於PostgreSQL允許使用者定義運算子,PostgreSQL無法查看運算子的名稱(例如,<>=),並判斷它屬於哪種類型的比較。相反,索引方法定義了一組策略,這些策略可以被認為是廣義的運算子。每個運算子類別都指定了對於特定資料類型和索引語意的解釋,哪個實際運算子對應於每個策略。

B 樹索引方法定義了五種策略,如表 36.3 所示。

表 36.3. B 樹策略

操作 策略編號
小於 1
小於或等於 2
等於 3
大於或等於 4
大於 5

雜湊索引僅支援等式比較,因此它們只使用一種策略,如表 36.4 所示。

表 36.4. 雜湊策略

操作 策略編號
等於 1

GiST 索引更加彈性:它們完全沒有固定的策略集合。相反,每個特定 GiST 運算子類別的「一致性」支援常式會以其喜歡的方式解讀策略編號。舉例來說,一些內建的 GiST 索引運算子類別索引二維幾何物件,並提供表 36.5中顯示的「R 樹」策略。其中四個是真正的二維測試(重疊、相同、包含、被包含);其中四個只考慮 X 軸方向;另外四個則在 Y 軸方向提供相同的測試。

表 36.5. GiST 二維「R 樹」策略

操作 策略編號
嚴格位於左側 1
未向右延伸 2
重疊 3
未向左延伸 4
嚴格位於右側 5
相同 6
包含 7
被包含 8
未向上延伸 9
嚴格位於下方 10
嚴格位於上方 11
未向下延伸 12

SP-GiST 索引在彈性方面與 GiST 索引相似:它們沒有固定的策略集合。相反,每個運算子類別的支援常式會根據運算子類別的定義來解讀策略編號。舉例來說,內建點運算子類別使用的策略編號顯示在表 36.6中。

表 36.6. SP-GiST 點策略

操作 策略編號
嚴格位於左側 1
嚴格位於右側 5
相同 6
被包含 8
嚴格位於下方 10
嚴格位於上方 11

GIN 索引在彈性方面與 GiST 和 SP-GiST 索引相似,因為它們也沒有固定的策略集合。相反,每個運算子類別的支援常式會根據運算子類別的定義來解讀策略編號。舉例來說,內建陣列運算子類別使用的策略編號顯示在表 36.7中。

表 36.7. GIN 陣列策略

操作 策略編號
重疊 1
包含 2
被包含 3
等於 4

BRIN 索引在彈性方面與 GiST、SP-GiST 和 GIN 索引相似,因為它們也沒有固定的策略集合。相反,每個運算子類別的支援常式會根據運算子類別的定義來解讀策略編號。舉例來說,內建的 Minmax 運算子類別使用的策略編號顯示在表 36.8中。

表 36.8. BRIN Minmax 策略

操作 策略編號
小於 1
小於或等於 2
等於 3
大於或等於 4
大於 5

請注意,上面列出的所有運算子都會傳回布林值。實際上,所有定義為索引方法搜尋運算子的運算子都必須傳回 boolean 類型,因為它們必須出現在 WHERE 子句的頂層,才能與索引一起使用。(某些索引存取方法也支援排序運算子,它們通常不傳回布林值;該功能在第 36.16.7 節中討論。)

36.16.3. 索引方法支援常式 #

通常,策略不足以讓系統知道如何使用索引。實際上,索引方法需要額外的支援常式才能運作。例如,B 樹索引方法必須能夠比較兩個鍵,並判斷其中一個是否大於、等於或小於另一個。同樣地,雜湊索引方法必須能夠計算鍵值的雜湊碼。這些操作與 SQL 命令中條件中的運算子不對應;它們是由索引方法在內部使用的管理常式。

與策略一樣,運算子類別會識別哪些特定函式應為給定的資料類型和語意解譯扮演這些角色。索引方法定義了它需要的函式集,而運算子類別透過將它們分配給索引方法指定的「支援函式編號」來識別要使用的正確函式。

此外,某些運算子類別允許使用者指定控制其行為的參數。每個內建索引存取方法都有一個可選的 options 支援函式,它定義了一組特定於運算子類別的參數。

B 樹需要一個比較支援函式,並允許在運算子類別作者的選項中提供四個額外的支援函式,如表 36.9所示。這些支援函式的需求在第 64.1.3 節中進一步說明。

表 36.9. B 樹支援函式

函式 支援編號
比較兩個鍵,並傳回一個小於零、零或大於零的整數,表示第一個鍵小於、等於或大於第二個鍵 1
傳回 C 可呼叫排序支援函式的位址(可選) 2
將測試值與基值加上/減去偏移量進行比較,並根據比較結果傳回 true 或 false(可選) 3
確定使用運算子類別的索引是否可以安全地應用 btree 重複資料刪除最佳化(可選) 4
定義特定於此運算子類別的選項(可選) 5

雜湊索引需要一個支援函式,並允許在運算子類別作者的選項中提供兩個額外的支援函式,如表 36.10所示。

表 36.10. 雜湊支援函式

函式 支援編號
計算鍵的 32 位元雜湊值 1
計算給定 64 位元鹽值的鍵的 64 位元雜湊值;如果鹽值為 0,則結果的低 32 位元必須與函式 1 計算的值相符(可選) 2
定義特定於此運算子類別的選項(可選) 3

GiST 索引有 11 個支援函式,其中 6 個是可選的,如表 36.11所示。(如需更多資訊,請參閱第 64.2 節。)

表 36.11. GiST 支援函式

函式 描述 支援編號
consistent (一致性) 判斷鍵是否滿足查詢條件 1
union (聯集) 計算一組鍵的聯集 2
compress (壓縮) 計算要建立索引的鍵或值的壓縮表示法(可選) 3
decompress (解壓縮) 計算壓縮鍵的解壓縮表示法(可選) 4
penalty (懲罰) 計算將新鍵插入具有給定子樹鍵的子樹的懲罰 5
picksplit (分割選擇) 確定頁面的哪些項目要移動到新頁面,並計算結果頁面的聯集鍵 6
相同 比較兩個鍵,如果它們相等則傳回 true 7
distance (距離) 確定鍵到查詢值的距離(可選) 8
fetch (提取) 計算壓縮鍵的原始表示式,用於僅索引掃描(可選) 9
選項 定義特定於此運算子類別的選項(可選) 10
sortsupport 提供一個排序比較器,用於快速索引建置(可選) 11

SP-GiST 索引有六個支援函數,其中一個是可選的,如表 36.12所示。(更多資訊請參閱第 64.3 節。)

表 36.12. SP-GiST 支援函數

函式 描述 支援編號
config 提供關於運算子類別的基本資訊 1
choose 決定如何將新值插入內部元組 2
picksplit (分割選擇) 決定如何分割一組值 3
inner_consistent 決定查詢需要搜尋哪些子分割區 4
leaf_consistent 判斷鍵是否滿足查詢條件 5
選項 定義特定於此運算子類別的選項(可選) 6

GIN 索引有七個支援函數,其中四個是可選的,如表 36.13所示。(更多資訊請參閱第 64.4 節。)

表 36.13. GIN 支援函數

函式 描述 支援編號
compare 比較兩個鍵,並返回一個小於零、零或大於零的整數,表示第一個鍵小於、等於或大於第二個鍵 1
extractValue 從要索引的值中提取鍵 2
extractQuery 從查詢條件中提取鍵 3
consistent (一致性) 決定值是否符合查詢條件(布林變體)(如果存在支援函數 6,則為可選) 4
comparePartial 比較來自查詢的部分鍵和來自索引的鍵,並返回一個小於零、零或大於零的整數,表示 GIN 應忽略此索引條目、將該條目視為匹配,或停止索引掃描(可選) 5
triConsistent 決定值是否符合查詢條件(三元變體)(如果存在支援函數 4,則為可選) 6
選項 定義特定於此運算子類別的選項(可選) 7

BRIN 索引有五個基本支援函數,其中一個是可選的,如表 36.14所示。基本函數的某些版本需要提供額外的支援函數。(更多資訊請參閱第 64.5.3 節。)

表 36.14. BRIN 支援函數

函式 描述 支援編號
opcInfo 返回描述索引欄位摘要資料的內部資訊 1
add_value 將新值新增到現有的摘要索引元組 2
consistent (一致性) 決定值是否符合查詢條件 3
union (聯集) 計算兩個摘要元組的聯集 4
選項 定義特定於此運算子類別的選項(可選) 5

與搜尋運算子不同,支援函數返回特定索引方法期望的任何資料類型;例如,對於 B 樹的比較函數,返回一個有符號整數。每個支援函數的引數數量和類型同樣取決於索引方法。對於 B 樹和雜湊,比較和雜湊支援函數採用與運算子類別中包含的運算子相同的輸入資料類型,但對於大多數 GiST、SP-GiST、GIN 和 BRIN 支援函數,情況並非如此。

36.16.4. 範例 #

現在我們已經瞭解了這些概念,這是創建新運算子類別的承諾範例。(您可以在原始碼發佈的 src/tutorial/complex.csrc/tutorial/complex.sql 中找到此範例的工作副本。)運算子類別封裝了按絕對值順序排序複數的運算子,因此我們選擇名稱 complex_abs_ops。首先,我們需要一組運算子。定義運算子的程序在第 36.14 節中討論。對於 B 樹上的運算子類別,我們需要的運算子是

  • 絕對值小於(策略 1)
  • 絕對值小於或等於(策略 2)
  • 絕對值等於(策略 3)
  • 絕對值大於或等於(策略 4)
  • 絕對值大於(策略 5)

定義相關的一組比較運算子,最不容易出錯的方法是先編寫 B 樹比較支援函數,然後將其他函數編寫為支援函數周圍的單行封裝。這減少了在邊緣情況下獲得不一致結果的可能性。按照這種方法,我們先編寫

#define Mag(c)  ((c)->x*(c)->x + (c)->y*(c)->y)

static int
complex_abs_cmp_internal(Complex *a, Complex *b)
{
    double      amag = Mag(a),
                bmag = Mag(b);

    if (amag < bmag)
        return -1;
    if (amag > bmag)
        return 1;
    return 0;
}

現在小於函數看起來像

PG_FUNCTION_INFO_V1(complex_abs_lt);

Datum
complex_abs_lt(PG_FUNCTION_ARGS)
{
    Complex    *a = (Complex *) PG_GETARG_POINTER(0);
    Complex    *b = (Complex *) PG_GETARG_POINTER(1);

    PG_RETURN_BOOL(complex_abs_cmp_internal(a, b) < 0);
}

其他四個函數的差異僅在於它們如何將內部函數的結果與零進行比較。

接下來,我們向 SQL 宣告基於這些函數的函數和運算子

CREATE FUNCTION complex_abs_lt(complex, complex) RETURNS bool
    AS 'filename', 'complex_abs_lt'
    LANGUAGE C IMMUTABLE STRICT;

CREATE OPERATOR < (
   leftarg = complex, rightarg = complex, procedure = complex_abs_lt,
   commutator = > , negator = >= ,
   restrict = scalarltsel, join = scalarltjoinsel
);

指定正確的交換運算子和否定運算子,以及合適的限制和聯集選擇性函數非常重要,否則最佳化器將無法有效地使用索引。

這裡還發生了其他值得注意的事情

  • 只能有一個名為(例如)= 並且將 complex 類型用於兩個運算元的運算子。在這種情況下,我們沒有任何其他用於 complex 的運算子 =,但如果我們要建置一個實用的資料類型,我們可能希望 = 成為複數的普通相等運算(而不是絕對值的相等)。在這種情況下,我們需要為 complex_abs_eq 使用其他運算子名稱。

  • 雖然 PostgreSQL 可以處理具有相同 SQL 名稱的函數,只要它們具有不同的引數資料類型,但 C 只能處理具有給定名稱的一個全域函數。因此,我們不應該將 C 函數命名為像 abs_eq 這樣簡單的名稱。通常,最好在 C 函數名稱中包含資料類型名稱,以免與其他資料類型的函數衝突。

  • 我們可以使函數的 SQL 名稱成為 abs_eq,並依靠 PostgreSQL 透過引數資料類型將其與任何其他具有相同名稱的 SQL 函數區分開來。為了使範例簡單,我們使函數在 C 層級和 SQL 層級具有相同的名稱。

下一步是註冊 B 樹所需的支援常式。實現此功能的範例 C 程式碼與包含運算子函數的檔案位於同一檔案中。這是我們宣告函數的方式

CREATE FUNCTION complex_abs_cmp(complex, complex)
    RETURNS integer
    AS 'filename'
    LANGUAGE C IMMUTABLE STRICT;

現在我們有了所需的運算子和支援常式,我們終於可以創建運算子類別了

CREATE OPERATOR CLASS complex_abs_ops
    DEFAULT FOR TYPE complex USING btree AS
        OPERATOR        1       < ,
        OPERATOR        2       <= ,
        OPERATOR        3       = ,
        OPERATOR        4       >= ,
        OPERATOR        5       > ,
        FUNCTION        1       complex_abs_cmp(complex, complex);

我們完成了!現在應該可以在 complex 欄位上建立和使用 B 樹索引了。

我們可以更冗長地編寫運算子條目,如

        OPERATOR        1       < (complex, complex) ,

但是當運算子採用與我們為其定義運算子類別的相同資料類型時,沒有必要這樣做。

上面的範例假設您希望使這個新的運算子類別成為 complex 資料類型的預設 B 樹運算子類別。如果您不想這樣做,只需省略單字 DEFAULT

36.16.5. 運算子類別和運算子家族 #

到目前為止,我們隱含地假設一個運算子類別只處理一種資料型別。雖然在特定的索引欄中確實只能有一種資料型別,但索引將索引欄位與不同資料型別的值比較的運算操作通常很有用。此外,如果在與運算子類別相關的情況下需要跨資料型別的運算子,通常另一種資料型別本身也具有相關的運算子類別。明確地建立相關類別之間的連結是有幫助的,因為這可以幫助查詢規劃器優化 SQL 查詢(特別是對於 B 樹運算子類別,因為規劃器包含大量關於如何使用它們的知識)。

為了處理這些需求,PostgreSQL 使用了運算子族系的概念。運算子族系包含一個或多個運算子類別,並且還可以包含可索引的運算子和相應的支援函數,它們屬於整個族系,但不屬於族系內的任何單一類別。我們說這些運算子和函數在族系內是鬆散的,而不是綁定到特定的類別。通常,每個運算子類別都包含單一資料型別的運算子,而跨資料型別的運算子在族系中是鬆散的。

運算子族系中的所有運算子和函數都必須具有相容的語意,其中相容性要求由索引方法設定。因此,您可能會想知道為什麼要將族系的特定子集區分為運算子類別;事實上,對於許多目的來說,類別劃分是無關緊要的,而族系才是唯一有趣的群組。定義運算子類別的原因是它們指定了支援任何特定索引需要族系中的多少部分。如果存在使用運算子類別的索引,則在不刪除索引的情況下無法刪除該運算子類別 - 但可以刪除運算子族系的其他部分,即其他運算子類別和鬆散的運算子。因此,應指定一個運算子類別,以包含合理地需要與特定資料型別的索引一起使用的最小運算子和函數集,然後可以將相關但不重要的運算子添加為運算子族系的鬆散成員。

例如,PostgreSQL 具有內建的 B 樹運算子族系 integer_ops,其中包括用於 bigint (int8)、integer (int4) 和 smallint (int2) 欄位上索引的運算子類別 int8_opsint4_opsint2_ops。該族系還包含跨資料型別的比較運算子,允許比較這些型別中的任何兩個,以便可以使用另一種型別的比較值來搜尋其中一種型別的索引。可以使用以下定義複製該族系

CREATE OPERATOR FAMILY integer_ops USING btree;

CREATE OPERATOR CLASS int8_ops
DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS
  -- standard int8 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint8cmp(int8, int8) ,
  FUNCTION 2 btint8sortsupport(internal) ,
  FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

CREATE OPERATOR CLASS int4_ops
DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS
  -- standard int4 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint4cmp(int4, int4) ,
  FUNCTION 2 btint4sortsupport(internal) ,
  FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

CREATE OPERATOR CLASS int2_ops
DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
  -- standard int2 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint2cmp(int2, int2) ,
  FUNCTION 2 btint2sortsupport(internal) ,
  FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

ALTER OPERATOR FAMILY integer_ops USING btree ADD
  -- cross-type comparisons int8 vs int2
  OPERATOR 1 < (int8, int2) ,
  OPERATOR 2 <= (int8, int2) ,
  OPERATOR 3 = (int8, int2) ,
  OPERATOR 4 >= (int8, int2) ,
  OPERATOR 5 > (int8, int2) ,
  FUNCTION 1 btint82cmp(int8, int2) ,

  -- cross-type comparisons int8 vs int4
  OPERATOR 1 < (int8, int4) ,
  OPERATOR 2 <= (int8, int4) ,
  OPERATOR 3 = (int8, int4) ,
  OPERATOR 4 >= (int8, int4) ,
  OPERATOR 5 > (int8, int4) ,
  FUNCTION 1 btint84cmp(int8, int4) ,

  -- cross-type comparisons int4 vs int2
  OPERATOR 1 < (int4, int2) ,
  OPERATOR 2 <= (int4, int2) ,
  OPERATOR 3 = (int4, int2) ,
  OPERATOR 4 >= (int4, int2) ,
  OPERATOR 5 > (int4, int2) ,
  FUNCTION 1 btint42cmp(int4, int2) ,

  -- cross-type comparisons int4 vs int8
  OPERATOR 1 < (int4, int8) ,
  OPERATOR 2 <= (int4, int8) ,
  OPERATOR 3 = (int4, int8) ,
  OPERATOR 4 >= (int4, int8) ,
  OPERATOR 5 > (int4, int8) ,
  FUNCTION 1 btint48cmp(int4, int8) ,

  -- cross-type comparisons int2 vs int8
  OPERATOR 1 < (int2, int8) ,
  OPERATOR 2 <= (int2, int8) ,
  OPERATOR 3 = (int2, int8) ,
  OPERATOR 4 >= (int2, int8) ,
  OPERATOR 5 > (int2, int8) ,
  FUNCTION 1 btint28cmp(int2, int8) ,

  -- cross-type comparisons int2 vs int4
  OPERATOR 1 < (int2, int4) ,
  OPERATOR 2 <= (int2, int4) ,
  OPERATOR 3 = (int2, int4) ,
  OPERATOR 4 >= (int2, int4) ,
  OPERATOR 5 > (int2, int4) ,
  FUNCTION 1 btint24cmp(int2, int4) ,

  -- cross-type in_range functions
  FUNCTION 3 in_range(int4, int4, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int4, int4, int2, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;

請注意,此定義多載了運算子策略和支援函數編號:每個編號在族系中多次出現。只要特定編號的每個實例都具有不同的輸入資料型別,這是允許的。將兩個輸入型別都等於運算子類別的輸入型別的實例是該運算子類別的主要運算子和支援函數,並且在大多數情況下應宣告為運算子類別的一部分,而不是作為族系的鬆散成員。

在 B 樹運算子族系中,族系中的所有運算子都必須相容地排序,如第 64.1.2 節中詳細說明的那樣。對於族系中的每個運算子,必須有一個具有與運算子相同的兩個輸入資料型別的支援函數。建議族系是完整的,即對於資料型別的每種組合,都包含所有運算子。每個運算子類別應僅包含其資料型別的非跨型別運算子和支援函數。

要建立多資料型別的雜湊運算子族系,必須為族系支援的每種資料型別建立相容的雜湊支援函數。此處的相容性表示保證函數對於族系的等號運算子認為相等的任何兩個值(即使這些值的型別不同)都返回相同的雜湊碼。當這些型別具有不同的物理表示形式時,通常很難實現,但在某些情況下可以實現。此外,透過隱式或二進位強制轉換將運算子族系中表示的一種資料型別的值轉換為運算子族系中表示的另一種資料型別,也不得更改計算的雜湊值。請注意,每種資料型別只有一個支援函數,而不是每個等號運算子有一個。建議族系是完整的,即為資料型別的每種組合提供一個等號運算子。每個運算子類別應僅包含其資料型別的非跨型別等號運算子和支援函數。

GiST、SP-GiST 和 GIN 索引沒有任何跨資料型別運算的明確概念。支援的運算子集只是給定運算子類別的主要支援函數可以處理的任何運算子。

在 BRIN 中,需求取決於提供運算子類別的架構。對於基於 minmax 的運算子類別,所需行為與 B 樹運算子族系相同:族系中的所有運算子都必須相容地排序,並且強制轉換不得更改相關的排序順序。

注意

PostgreSQL 8.3 之前,沒有運算子族系的概念,因此任何打算與索引一起使用的跨資料型別運算子都必須直接綁定到索引的運算子類別中。雖然此方法仍然有效,但它已被棄用,因為它使索引的依賴項過於寬泛,並且當兩種資料型別在同一個運算子族系中都具有運算子時,規劃器可以更有效地處理跨資料型別的比較。

36.16.6. 運算子類別的系統依賴項 #

PostgreSQL 使用運算子類別來推斷運算子的屬性,而不僅僅是它們是否可以與索引一起使用。因此,即使您不打算索引資料型別的任何欄位,您也可能想要建立運算子類別。

特別是,諸如 ORDER BYDISTINCT 之類的 SQL 功能需要比較和排序值。為了在使用者定義的資料型別上實現這些功能,PostgreSQL 會尋找該資料型別的預設 B 樹運算子類別。此運算子類別的等於成員定義了系統對 GROUP BYDISTINCT 的值的相等性的概念,以及運算子類別強加的排序順序定義了預設的 ORDER BY 排序。

如果資料型別沒有預設的 B 樹運算子類別,系統將尋找預設的雜湊運算子類別。但是由於這種運算子類別僅提供相等性,因此它只能支援分組而不能排序。

如果資料型別沒有預設的運算子類別,如果您嘗試將這些 SQL 功能與資料型別一起使用,您將收到類似於無法識別排序運算子的錯誤。

注意

在 7.4 之前的 PostgreSQL 版本中,排序和分組操作會隱式使用名為 =<> 的運算子。依賴預設運算子類別的新行為避免了對具有特定名稱的運算子的行為做出任何假設。

可以透過在 USING 選項中指定類別的小於運算子來按非預設的 B 樹運算子類別進行排序,例如

SELECT * FROM mytable ORDER BY somecol USING ~<~;

或者,在 USING 中指定類別的大於運算子會選擇降序排序。

使用者定義型別的陣列比較也依賴於該型別預設 B 樹運算子類別定義的語意。如果沒有預設的 B 樹運算子類別,但有一個預設的雜湊運算子類別,則支援陣列相等性,但不支援排序比較。

另一個需要更多資料類型特定知識的 SQL 功能是視窗函數的 RANGE offset PRECEDING/FOLLOWING 框架選項(請參閱第 4.2.8 節)。對於像這樣的查詢

SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
  FROM mytable;

僅知道如何按 x 排序是不夠的;資料庫還必須理解如何對 x 的目前列的值減去 5加上 10,以識別目前視窗框架的邊界。使用 B 樹運算子類別提供的比較運算子(該類別定義了 ORDER BY 排序)可以比較產生的邊界與其他列的 x 值,但是加法和減法運算子不是運算子類別的一部分,所以應該使用哪些運算子呢?硬性規定這種選擇是不可取的,因為不同的排序方式(不同的 B 樹運算子類別)可能需要不同的行為。因此,B 樹運算子類別可以指定一個 in_range 支援函數,該函數封裝了對其排序方式有意義的加法和減法行為。它甚至可以提供多個 in_range 支援函數,以防有多個資料類型適合用作 RANGE 子句中的偏移量。如果與視窗的 ORDER BY 子句相關聯的 B 樹運算子類別沒有符合的 in_range 支援函數,則不支援 RANGE offset PRECEDING/FOLLOWING 選項。

另一個重點是,出現在雜湊運算子系列中的等式運算子是雜湊聯結、雜湊聚合和相關最佳化的候選者。雜湊運算子系列在這裡至關重要,因為它識別要使用的雜湊函數。

36.16.7. 排序運算子 #

某些索引存取方法(目前,只有 GiST 和 SP-GiST)支援排序運算子的概念。到目前為止,我們討論的是搜尋運算子。搜尋運算子是索引可以被搜尋以找到所有滿足 WHERE indexed_column operator constant 的列的運算子。請注意,不保證以何種順序傳回符合的列。相反地,排序運算子不限制可以傳回的列集合,而是決定它們的順序。排序運算子是索引可以被掃描以傳回 ORDER BY indexed_column operator constant 所代表的順序的列的運算子。以這種方式定義排序運算子的原因是,如果該運算子是測量距離的運算子,它支援最近鄰搜尋。例如,像這樣的查詢

SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;

找到最靠近給定目標點的十個位置。位置欄位上的 GiST 索引可以有效地執行此操作,因為 <-> 是一個排序運算子。

雖然搜尋運算子必須傳回布林值結果,但排序運算子通常傳回一些其他類型,例如用於距離的 float 或 numeric。此類型通常與索引的資料類型不同。為了避免對不同資料類型的行為進行硬性假設,排序運算子的定義需要指定一個 B 樹運算子系列,該系列指定結果資料類型的排序方式。如上一節所述,B 樹運算子系列定義了 PostgreSQL 的排序概念,因此這是一種自然的表示形式。由於點 <-> 運算子傳回 float8,因此可以在運算子類別建立命令中指定它,如下所示

OPERATOR 15    <-> (point, point) FOR ORDER BY float_ops

其中 float_ops 是內建運算子系列,包含 float8 的運算。此宣告說明索引能夠以 <-> 運算子的遞增值順序傳回列。

36.16.8. 運算子類別的特殊功能 #

我們還有兩個尚未討論的運算子類別的特殊功能,主要是因為它們對於最常用的索引方法沒有用處。

通常,將運算子宣告為運算子類別(或系列)的成員意味著索引方法可以精確地檢索滿足使用該運算子的 WHERE 條件的列集合。例如

SELECT * FROM table WHERE integer_column < 4;

可以通過整數欄位上的 B 樹索引精確滿足。但是,在某些情況下,索引可以用作匹配列的不精確指南。例如,如果 GiST 索引僅儲存幾何物件的邊界框,則它不能完全滿足測試非矩形物件(例如多邊形)之間重疊的 WHERE 條件。但是,我們可以通過使用索引來尋找其邊界框與目標物件的邊界框重疊的物件,然後僅對索引找到的物件執行精確的重疊測試。如果此情況適用,則稱該索引對於該運算子是有損的。透過讓索引方法在列可能真的滿足查詢條件,或可能不滿足查詢條件時傳回 recheck 標記來實作有損索引搜尋。然後,核心系統將測試檢索到的列上的原始查詢條件,以查看是否應將其作為有效匹配項傳回。如果索引保證傳回所有需要的列,以及可能的一些額外列(可以通過執行原始運算子調用來消除),則此方法有效。支援有損搜尋的索引方法(目前為 GiST、SP-GiST 和 GIN)允許個別運算子類別的支援函數設定 recheck 標記,因此這本質上是一個運算子類別功能。

再次考慮我們僅在索引中儲存複雜物件(例如多邊形)的邊界框的情況。在這種情況下,在索引條目中儲存整個多邊形沒有太大的價值,我們最好只儲存一個更簡單的 box 類型的物件。這種情況通過 CREATE OPERATOR CLASS 中的 STORAGE 選項來表達:我們會寫類似於

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

目前,只有 GiST、SP-GiST、GIN 和 BRIN 索引方法支援與欄位資料類型不同的 STORAGE 類型。使用 STORAGE 時,GiST compressdecompress 支援常式必須處理資料類型轉換。SP-GiST 同樣需要 compress 支援函數來轉換為儲存類型(如果該類型不同);如果 SP-GiST opclass 也支援檢索資料,則反向轉換必須由 consistent 函數處理。在 GIN 中,STORAGE 類型識別值的類型,該類型通常與索引欄位的類型不同,例如,整數陣列欄位的運算子類別可能具有只是整數的鍵。GIN extractValueextractQuery 支援常式負責從索引值中提取鍵。BRIN 類似於 GIN:STORAGE 類型識別儲存的摘要值的類型,並且運算子類別的支援程序負責正確解譯摘要值。

提交更正

如果您在文件中發現任何不正確、與您特定功能的使用經驗不符,或需要進一步釐清之處,請使用此表單回報文件問題。