GIN代表 Generalized Inverted Index(廣義反向索引)。GIN專為處理需要索引的項目是複合值,且索引要處理的查詢需要搜尋複合項目中出現的元素值的情況而設計。例如,這些項目可能是文件,而查詢可能是搜尋包含特定字詞的文件。
我們使用「項目」一詞來指稱要索引的複合值,並使用「鍵」一詞來指稱元素值。GIN永遠儲存和搜尋鍵,而不是項目值本身。
一個GIN索引儲存一組(鍵,文章清單)配對,其中「文章清單」是一組鍵出現的列 ID。相同的列 ID 可能會出現在多個文章清單中,因為一個項目可能包含多個鍵。每個鍵值只儲存一次,因此,GIN索引對於相同鍵出現多次的情況來說非常精簡。
GIN之所以說是廣義的,是因為GIN存取方法程式碼不需要知道它加速的特定運算。相反地,它使用為特定資料類型定義的自訂策略。該策略定義了如何從索引項目和查詢條件中提取鍵,以及如何判斷包含查詢中某些鍵值的列是否確實滿足查詢。
的優點之一GIN是它允許由資料類型領域的專家(而不是資料庫專家)開發具有適當存取方法的自訂資料類型。這與使用GiST.
的優點非常相似。GIN在 PostgreSQL 中的實作主要由 Teodor Sigaev 和 Oleg Bartunov 維護。更多關於GIN的資訊,請參閱他們的網站。
核心 PostgreSQL 發行版本包含如 表 64.3 所示的GIN運算子類別。(在附錄 F 中描述的一些選用模組提供了額外的GIN運算子類別。)
表 64.3. 內建的GIN運算子類別
名稱 | 可索引的運算子 |
---|---|
array_ops |
&& (anyarray,anyarray) |
@> (anyarray,anyarray) |
|
<@ (anyarray,anyarray) |
|
= (anyarray,anyarray) |
|
jsonb_ops |
@> (jsonb,jsonb) |
@? (jsonb,jsonpath) |
|
@@ (jsonb,jsonpath) |
|
? (jsonb,text) |
|
?| (jsonb,text[]) |
|
?& (jsonb,text[]) |
|
jsonb_path_ops |
@> (jsonb,jsonb) |
@? (jsonb,jsonpath) |
|
@@ (jsonb,jsonpath) |
|
tsvector_ops |
@@ (tsvector,tsquery) |
在 jsonb
類型的兩個運算子類別中,jsonb_ops
是預設值。jsonb_path_ops
支援的運算子較少,但對於這些運算子來說,效能更好。請參閱第 8.14.4 節,以取得詳細資訊。
的優點非常相似。GIN介面具有高階抽象,僅要求存取方法實作者實作所存取資料類型的語意。TheGIN層本身負責並行、記錄和搜尋樹狀結構。
要讓GIN存取方法運作,只需實作一些使用者定義的方法,這些方法定義樹狀結構中鍵的行為,以及鍵、索引項目和可索引查詢之間的關係。簡而言之,GIN結合了擴充性、通用性、程式碼重複使用和乾淨的介面。
針對GIN的運算子類別必須提供的兩種方法
Datum *extractValue(Datum itemValue, int32 *nkeys, bool **nullFlags)
傳回給定要索引的項目之鍵的 palloc'd 陣列。傳回的鍵的數量必須儲存在 *nkeys
中。如果任何鍵可以為空值,則還應 palloc 一個 *nkeys
bool
欄位的陣列,將其位址儲存在 *nullFlags
,並根據需要設定這些空值旗標。如果所有鍵都不是空值,則可以將 *nullFlags
保持 NULL
(其初始值)。如果該項目不包含任何鍵,則傳回值可以為 NULL
。
Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)
傳回一個 palloc 配置的鍵陣列,給定一個要查詢的值;也就是說,query
是可索引運算子右側的值,而該運算子的左側是已索引的欄位。n
是運算子類別中運算子的策略編號(請參閱第 36.16.2 節)。通常,extractQuery
需要參考 n
來確定 query
的資料類型以及它應該用來提取鍵值的方法。傳回的鍵的數量必須儲存在 *nkeys
中。如果任何鍵可以是空值,則也 palloc 一個 *nkeys
個 bool
欄位的陣列,將其位址儲存在 *nullFlags
,並根據需要設定這些空值旗標。如果所有鍵都不是空值,則 *nullFlags
可以保持 NULL
(其初始值)。如果 query
不包含任何鍵,則傳回值可以是 NULL
。
searchMode
是一個輸出引數,允許 extractQuery
指定有關如何執行搜尋的詳細資訊。如果 *searchMode
設定為 GIN_SEARCH_MODE_DEFAULT
(這是呼叫前初始化的值),則只會將至少符合其中一個傳回鍵的項目視為候選符合項。如果 *searchMode
設定為 GIN_SEARCH_MODE_INCLUDE_EMPTY
,則除了包含至少一個符合鍵的項目之外,不包含任何鍵的項目也會被視為候選符合項。(例如,此模式適用於實作 is-subset-of 運算子。)如果 *searchMode
設定為 GIN_SEARCH_MODE_ALL
,則索引中的所有非空值項目都會被視為候選符合項,無論它們是否符合任何傳回的鍵。(此模式比其他兩種選擇慢得多,因為它需要掃描基本上整個索引,但可能需要正確地實作邊緣案例。在大多數情況下需要此模式的運算子可能不是 GIN 運算子類別的良好候選者。)用於設定此模式的符號定義在 access/gin.h
中。
pmatch
是一個輸出引數,用於支援部分符合的情況。要使用它,extractQuery
必須配置一個 *nkeys
個 bool
的陣列,並將其位址儲存在 *pmatch
。如果對應的鍵需要部分符合,則陣列的每個元素都應設定為 true,否則設定為 false。如果 *pmatch
設定為 NULL
,則 GIN 假設不需要部分符合。在呼叫之前,變數會初始化為 NULL
,因此不支援部分符合的運算子類別可以簡單地忽略此引數。
extra_data
是一個輸出引數,允許 extractQuery
將額外資料傳遞給 consistent
和 comparePartial
方法。要使用它,extractQuery
必須配置一個 *nkeys
個指標的陣列,並將其位址儲存在 *extra_data
,然後將它想要的任何內容儲存在個別指標中。在呼叫之前,變數會初始化為 NULL
,因此不需要額外資料的運算子類別可以簡單地忽略此引數。如果設定了 *extra_data
,則整個陣列會傳遞給 consistent
方法,並且適當的元素會傳遞給 comparePartial
方法。
運算子類別還必須提供一個函數來檢查已索引的項目是否符合查詢。它有兩種形式,一個布林值的 consistent
函數和一個三元 triConsistent
函數。triConsistent
涵蓋兩者的功能,因此僅提供 triConsistent
就足夠了。但是,如果布林值變體的計算成本顯著較低,則提供兩者可能是有利的。如果僅提供布林值變體,則會停用某些在提取所有鍵之前取反索引項目的最佳化。
bool consistent(bool check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], bool *recheck, Datum queryKeys[], bool nullFlags[])
如果已索引的項目滿足具有策略編號 n
的查詢運算子,則傳回 true(或者如果傳回了重新檢查指示,則可能滿足它)。此函數無法直接存取已索引的項目值,因為GIN不顯式儲存項目。相反,可用的是關於從查詢中提取的哪些鍵值出現在給定的已索引項目中的知識。check
陣列的長度為 nkeys
,與 extractQuery
先前為此 query
資料傳回的鍵數相同。check
陣列的每個元素,如果已索引的項目包含對應的查詢鍵,則為 true,也就是說,如果 (check[i] == true),則已索引的項目中存在 extractQuery
結果陣列的第 i 個鍵。傳遞原始的 query
資料,以防 consistent
方法需要諮詢它,並且先前由 extractQuery
傳回的 queryKeys[]
和 nullFlags[]
陣列也是如此。extra_data
是由 extractQuery
傳回的額外資料陣列,如果沒有則為 NULL
。
當 extractQuery
在 queryKeys[]
中傳回空值鍵時,如果已索引的項目包含空值鍵,則對應的 check[]
元素為 true;也就是說,check[]
的語意類似於 IS NOT DISTINCT FROM
。consistent
函數可以檢查對應的 nullFlags[]
元素,如果它需要區分規則值符合和空值符合。
成功時,如果需要根據查詢運算子重新檢查堆積元組,則應將 *recheck
設定為 true,如果索引測試是精確的,則應設定為 false。也就是說,false 傳回值保證堆積元組與查詢不符;*recheck
設定為 false 的 true 傳回值保證堆積元組與查詢匹配;而 *recheck
設定為 true 的 true 傳回值表示堆積元組可能與查詢匹配,因此需要提取並透過直接針對原始已索引項目評估查詢運算子來重新檢查它。
GinTernaryValue triConsistent(GinTernaryValue check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], Datum queryKeys[], bool nullFlags[])
triConsistent
函式與 consistent
函式相似,但 check
向量中的元素不再是布林值,而是每個鍵有三個可能的值:GIN_TRUE
、GIN_FALSE
和 GIN_MAYBE
。GIN_FALSE
和 GIN_TRUE
的含義與一般的布林值相同,而 GIN_MAYBE
則表示該鍵的存在與否未知。當存在 GIN_MAYBE
值時,只有在無論索引項目是否包含對應的查詢鍵,該項目*一定*匹配時,函式才應返回 GIN_TRUE
。同樣地,只有在無論是否包含 GIN_MAYBE
鍵,該項目*一定*不匹配時,函式才必須返回 GIN_FALSE
。如果結果取決於 GIN_MAYBE
條目,也就是說,無法根據已知的查詢鍵確認或反駁匹配,則該函式必須返回 GIN_MAYBE
。
當 check
向量中沒有 GIN_MAYBE
值時,GIN_MAYBE
的回傳值等同於在布林 consistent
函式中設定 recheck
旗標。
此外,GIN 必須有一種方法來排序儲存在索引中的鍵值。運算子類別可以透過指定比較方法來定義排序。
int compare(Datum a, Datum b)
比較兩個鍵(不是索引項目!),並返回一個小於零、零或大於零的整數,表示第一個鍵小於、等於或大於第二個鍵。Null 鍵永遠不會傳遞給此函式。
或者,如果運算子類別沒有提供 compare
方法,GIN 會查找索引鍵資料類型的預設 B-tree 運算子類別,並使用其比較函式。建議在僅用於一種資料類型的 GIN 運算子類別中指定比較函式,因為查找 B-tree 運算子類別會花費一些週期。但是,多型 GIN 運算子類別(例如 array_ops
)通常無法指定單一比較函式。
一個運算子類別用於GIN可以選擇性地提供以下方法
int comparePartial(Datum partial_key, Datum key, StrategyNumber n, Pointer extra_data)
將部分匹配查詢鍵與索引鍵進行比較。返回一個整數,其符號表示結果:小於零表示索引鍵與查詢不匹配,但應繼續索引掃描;零表示索引鍵與查詢匹配;大於零表示應停止索引掃描,因為不可能有更多匹配項。提供了生成部分匹配查詢的運算子的策略編號 n
,以防需要其語意來確定何時結束掃描。此外,extra_data
是 extractQuery
建立的額外資料陣列的對應元素,如果沒有則為 NULL
。Null 鍵永遠不會傳遞給此函式。
void options(local_relopts *relopts)
定義一組使用者可見的參數,用於控制運算子類別的行為。
options
函式會傳遞一個指向 local_relopts
結構的指標,該結構需要填入一組運算子類別特定的選項。可以使用 PG_HAS_OPCLASS_OPTIONS()
和 PG_GET_OPCLASS_OPTIONS()
巨集從其他支援函式存取這些選項。
由於索引值的鍵提取和鍵的表示在GIN中是彈性的,因此它們可能取決於使用者指定的參數。
為了支援「部分匹配」查詢,運算子類別必須提供 comparePartial
方法,並且其 extractQuery
方法必須在遇到部分匹配查詢時設定 pmatch
參數。有關詳細資訊,請參閱 第 64.4.4.2 節。
上面提到的各種 Datum
值的實際資料類型取決於運算子類別。extractValue
傳遞的項目值始終是運算子類別的輸入類型,並且所有鍵值必須是該類別的 STORAGE
類型。extractQuery
、consistent
和 triConsistent
傳遞的 query
引數的類型是透過策略編號識別的類別成員運算子的右手輸入類型。只要可以從中提取正確類型的鍵值,這就不需要與索引類型相同。但是,建議這三個支援函式的 SQL 宣告使用運算子類別的索引資料類型作為 query
引數,即使實際類型可能是其他類型(取決於運算子)。
在內部,一個GIN索引包含一個在鍵上建構的 B-tree 索引,其中每個鍵是一個或多個索引項目(例如陣列的成員)的元素,並且葉子頁面中的每個元組包含一個指向堆疊指標的 B-tree 的指標(一個「posting tree」),或者是一個簡單的堆疊指標列表(一個「posting list」),當該列表小到足以與鍵值一起放入單個索引元組中時。圖 64.1 說明了 GIN 索引的這些元件。
從 PostgreSQL 9.1 開始,索引中可以包含 Null 鍵值。此外,索引中還包含佔位符 Null,用於根據 extractValue
判斷為 Null 或不包含鍵的索引項目。這允許搜尋應該找到空項目的搜尋這樣做。
多欄位GIN索引是透過在複合值(欄位編號、鍵值)上建構單個 B-tree 來實現的。不同欄位的鍵值可以是不同的類型。
圖 64.1. GIN 內部結構
由於反向索引的固有性質,更新一個GIN索引往往很慢:插入或更新一個堆疊列可能會導致許多插入到索引中(每個從索引項目中提取的鍵一個)。GIN能夠透過將新元組插入到臨時的、未排序的待處理條目列表中來延遲大部分工作。當表格被 vacuum 或 autoanalyze 時,或者當呼叫 gin_clean_pending_list
函式時,或者如果待處理列表大於 gin_pending_list_limit 時,條目會使用初始索引建立期間使用的相同批量插入技術移動到主要的GIN資料結構。即使計算額外的 vacuum 負擔,這也大大提高了GIN索引更新速度。此外,負擔工作可以由背景程序完成,而不是在前台查詢處理中完成。
這種方法的主要缺點是搜尋必須掃描待處理條目列表,除了搜尋常規索引之外,因此大量的待處理條目列表會顯著減慢搜尋速度。另一個缺點是,雖然大多數更新都很快,但導致待處理列表變得「太大」的更新將會立即產生清理週期,因此會比其他更新慢得多。正確使用 autovacuum 可以最大限度地減少這兩個問題。
如果一致的回應時間比更新速度更重要,則可以透過關閉 fastupdate
儲存參數來停用待處理條目的使用,用於一個GIN索引。有關詳細資訊,請參閱 CREATE INDEX。
GIN 可以支援「部分匹配」查詢,在這種查詢中,查詢不會決定一個或多個鍵的精確匹配,但可能的匹配落在一個合理範圍的鍵值內(在由 compare
支援方法確定的鍵排序順序內)。 extractQuery
方法不是返回要精確匹配的鍵值,而是返回要搜索的範圍的下限鍵值,並將 pmatch
標記設定為 true。 然後使用 comparePartial
方法掃描鍵範圍。 comparePartial
必須為匹配的索引鍵返回零,對於仍在要搜索範圍內的非匹配項返回小於零的值,如果索引鍵超過可能匹配的範圍則返回大於零的值。
插入到一個GIN索引中可能會很慢,因為每個項目都可能插入許多鍵。 因此,對於大量插入到一個表中,建議刪除 GIN 索引,並在完成大量插入後重新建立它。
當啟用 fastupdate
時,GIN(有關詳細資訊,請參閱第 64.4.4.1 節),其懲罰會小於未啟用時。 但對於非常大的更新,最好還是刪除並重新建立索引。
建立一個GIN索引的時間對 maintenance_work_mem
設定非常敏感; 在索引建立期間,不應吝嗇工作記憶體。
在一系列插入到現有GIN啟用 fastupdate
的索引中時,系統將在列表大小超過 gin_pending_list_limit
時清除待處理項目列表。 為了避免觀察到的響應時間出現波動,最好在背景執行待處理列表清除(即透過 autovacuum)。 可以透過增加 gin_pending_list_limit
或使 autovacuum 更積極來避免前台清除作業。 但是,擴大清除作業的閾值意味著如果確實發生前台清除,則需要更長的時間。
可以透過更改儲存參數來覆寫個別 GIN 索引的 gin_pending_list_limit
,這允許每個 GIN 索引都有自己的清除閾值。 例如,可以僅針對可以大量更新的 GIN 索引增加閾值,而在其他情況下則降低閾值。
開發GIN索引的主要目標是在 PostgreSQL 中建立對高度可擴展全文檢索的支持,並且在全文檢索返回大量結果集時,通常會發生這種情況。 此外,當查詢包含非常頻繁的字詞時,通常會發生這種情況,因此大型結果集甚至沒有用。 由於從磁碟讀取許多元組並對它們進行排序可能需要很長時間,因此這對於生產環境來說是不可接受的。(請注意,索引搜尋本身非常快。)
為了方便控制此類查詢的執行,GIN對返回的行數有一個可配置的軟上限:gin_fuzzy_search_limit
配置參數。 預設情況下,它設定為 0(表示沒有限制)。 如果設定了非零限制,則返回的集合是整個結果集的一個子集,隨機選擇。
「軟性」表示實際返回的結果數可能會與指定的限制略有不同,具體取決於查詢和系統的隨機數產生器的品質。
根據經驗,數千個值(例如 5000 — 20000)效果很好。
GIN假設可索引運算符是嚴格的。 這表示根本不會對空項目值呼叫 extractValue
(而是自動建立佔位符索引項目),並且也不會對空查詢值呼叫 extractQuery
(而是假定查詢無法滿足)。 但是請注意,支援包含在非空複合項目或查詢值中的空鍵值。
如果您在文件中看到任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的內容,請使用此表單來報告文件問題。