2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5

65.5. BRIN 索引 #

65.5.1. 簡介 #

BRIN代表“塊範圍索引”(Block Range Index)。BRIN旨在處理非常大的表,其中某些列與它們在表中的物理位置具有某種自然相關性。

BRIN它透過塊範圍(或頁範圍)工作。塊範圍是表中物理上相鄰的頁面組;對於每個塊範圍,索引會儲存一些摘要資訊。例如,儲存商店銷售訂單的表可能有一個日期列,訂單是在該日期下達的,而大多數時候,較早訂單的條目也會更早地出現在表中;儲存郵政編碼列的表可能會自然地將一個城市的所有郵政編碼分組在一起。

BRIN索引可以透過常規的點陣圖索引掃描來滿足查詢,如果索引儲存的摘要資訊與查詢條件一致,它將返回每個範圍內的所有元組。查詢執行器負責重新檢查這些元組並丟棄不匹配查詢條件的元組——換句話說,這些索引是“有損的”。由於一個BRIN索引非常小,掃描索引相對於順序掃描的開銷很小,但可能會避免掃描表中已知不包含匹配元組的大部分割槽域。

一個BRIN索引將儲存的具體資料,以及索引能夠滿足的特定查詢,取決於為索引的每個列選擇的運算子類。具有線性排序順序的資料型別可以具有運算子類,這些類儲存每個塊範圍內的最小值和最大值,例如;幾何型別可能儲存塊範圍內所有物件的邊界框。

塊範圍的大小在索引建立時由 pages_per_range 儲存引數確定。索引條目的數量將等於關係的大小(以頁為單位)除以所選的 pages_per_range 值。因此,數字越小,索引越大(因為需要儲存更多的索引條目),但同時儲存的摘要資料可以更精確,並且在索引掃描期間可以跳過更多資料塊。

65.5.1.1. 索引維護 #

建立時,會掃描所有現有的堆頁,併為每個範圍建立一個摘要索引元組,包括末尾可能不完整的範圍。隨著新頁面的填充資料,已經摘要的頁面範圍將導致摘要資訊使用新元組中的資料進行更新。當建立一個不屬於最後一個摘要範圍的新頁面時,該新頁面所屬的範圍不會自動獲得摘要元組;這些元組將保持未摘要狀態,直到稍後呼叫摘要執行,為該範圍建立初始摘要。

有幾種方法可以觸發頁面範圍的初始摘要。如果表被 vacuum,無論是手動還是透過 autovacuum,所有現有的未摘要頁面範圍都將被摘要。此外,如果索引的 autosummarize 引數被啟用(預設情況下未啟用),則每當 autovacuum 在該資料庫中執行時,所有已填充的未摘要頁面範圍都將進行摘要,而無論表本身是否由 autovacuum 處理;見下文。

最後,可以使用以下函式(當這些函式執行時,search_path 會暫時更改為 pg_catalog, pg_temp):

brin_summarize_new_values(regclass),它會摘要所有未摘要的範圍;
brin_summarize_range(regclass, bigint),它只摘要包含給定頁面的範圍(如果該頁面是未摘要的)。

當啟用自動摘要時,會向 autovacuum 傳送一個請求,在檢測到下一個塊範圍的第一頁的第一個項被插入時,執行針對該塊範圍的摘要,該請求將在下一個 autovacuum 工作程序在同一個資料庫中完成執行時被滿足。如果請求佇列已滿,則不會記錄該請求,並且會向伺服器日誌傳送一條訊息。

LOG:  request for BRIN range summarization for index "brin_wi_idx" page 128 was not recorded

發生這種情況時,該範圍將保持未摘要狀態,直到對錶進行下一次常規 vacuum 執行,或者呼叫上面提到的函式之一。

反之,可以使用 brin_desummarize_range(regclass, bigint) 函式對範圍進行反摘要,這在索引元組不再是很好的表示(因為現有值已更改)時很有用。有關詳細資訊,請參閱 第 9.28.8 節

65.5.2. 內建運算子類 #

核心 PostgreSQL 發行版包含BRIN表 65.4 中所示的運算子類。

minmax 運算子類儲存在範圍內的索引列中出現的值的最小值和最大值。inclusion 運算子類儲存一個包含範圍內的索引列中的值的單個值。bloom 運算子類為範圍內的所有值構建一個 Bloom 過濾器。minmax-multi 運算子類儲存多個最小值和最大值,代表在範圍內的索引列中出現的值。

表 65.4. 內建BRIN運算子類

名稱 可索引運算子
bit_minmax_ops = (bit,bit)
< (bit,bit)
> (bit,bit)
<= (bit,bit)
>= (bit,bit)
box_inclusion_ops @> (box,point)
<< (box,box)
&< (box,box)
&> (box,box)
>> (box,box)
<@ (box,box)
@> (box,box)
~= (box,box)
&& (box,box)
<<| (box,box)
&<| (box,box)
|&> (box,box)
|>> (box,box)
bpchar_bloom_ops = (character,character)
bpchar_minmax_ops = (character,character)
< (character,character)
<= (character,character)
> (character,character)
>= (character,character)
bytea_bloom_ops = (bytea,bytea)
bytea_minmax_ops = (bytea,bytea)
< (bytea,bytea)
<= (bytea,bytea)
> (bytea,bytea)
>= (bytea,bytea)
char_bloom_ops = ("char","char")
char_minmax_ops = ("char","char")
< ("char","char")
<= ("char","char")
> ("char","char")
>= ("char","char")
date_bloom_ops = (date,date)
date_minmax_ops = (date,date)
< (date,date)
<= (date,date)
> (date,date)
>= (date,date)
date_minmax_multi_ops = (date,date)
< (date,date)
<= (date,date)
> (date,date)
>= (date,date)
float4_bloom_ops = (float4,float4)
float4_minmax_ops = (float4,float4)
< (float4,float4)
> (float4,float4)
<= (float4,float4)
>= (float4,float4)
float4_minmax_multi_ops = (float4,float4)
< (float4,float4)
> (float4,float4)
<= (float4,float4)
>= (float4,float4)
float8_bloom_ops = (float8,float8)
float8_minmax_ops = (float8,float8)
< (float8,float8)
<= (float8,float8)
> (float8,float8)
>= (float8,float8)
float8_minmax_multi_ops = (float8,float8)
< (float8,float8)
<= (float8,float8)
> (float8,float8)
>= (float8,float8)
inet_inclusion_ops << (inet,inet)
<<= (inet,inet)
>> (inet,inet)
>>= (inet,inet)
= (inet,inet)
&& (inet,inet)
inet_bloom_ops = (inet,inet)
inet_minmax_ops = (inet,inet)
< (inet,inet)
<= (inet,inet)
> (inet,inet)
>= (inet,inet)
inet_minmax_multi_ops = (inet,inet)
< (inet,inet)
<= (inet,inet)
> (inet,inet)
>= (inet,inet)
int2_bloom_ops = (int2,int2)
int2_minmax_ops = (int2,int2)
< (int2,int2)
> (int2,int2)
<= (int2,int2)
>= (int2,int2)
int2_minmax_multi_ops = (int2,int2)
< (int2,int2)
> (int2,int2)
<= (int2,int2)
>= (int2,int2)
int4_bloom_ops = (int4,int4)
int4_minmax_ops = (int4,int4)
< (int4,int4)
> (int4,int4)
<= (int4,int4)
>= (int4,int4)
int4_minmax_multi_ops = (int4,int4)
< (int4,int4)
> (int4,int4)
<= (int4,int4)
>= (int4,int4)
int8_bloom_ops = (bigint,bigint)
int8_minmax_ops = (bigint,bigint)
< (bigint,bigint)
> (bigint,bigint)
<= (bigint,bigint)
>= (bigint,bigint)
int8_minmax_multi_ops = (bigint,bigint)
< (bigint,bigint)
> (bigint,bigint)
<= (bigint,bigint)
>= (bigint,bigint)
interval_bloom_ops = (interval,interval)
interval_minmax_ops = (interval,interval)
< (interval,interval)
<= (interval,interval)
> (interval,interval)
>= (interval,interval)
interval_minmax_multi_ops = (interval,interval)
< (interval,interval)
<= (interval,interval)
> (interval,interval)
>= (interval,interval)
macaddr_bloom_ops = (macaddr,macaddr)
macaddr_minmax_ops = (macaddr,macaddr)
< (macaddr,macaddr)
<= (macaddr,macaddr)
> (macaddr,macaddr)
>= (macaddr,macaddr)
macaddr_minmax_multi_ops = (macaddr,macaddr)
< (macaddr,macaddr)
<= (macaddr,macaddr)
> (macaddr,macaddr)
>= (macaddr,macaddr)
macaddr8_bloom_ops = (macaddr8,macaddr8)
macaddr8_minmax_ops = (macaddr8,macaddr8)
< (macaddr8,macaddr8)
<= (macaddr8,macaddr8)
> (macaddr8,macaddr8)
>= (macaddr8,macaddr8)
macaddr8_minmax_multi_ops = (macaddr8,macaddr8)
< (macaddr8,macaddr8)
<= (macaddr8,macaddr8)
> (macaddr8,macaddr8)
>= (macaddr8,macaddr8)
name_bloom_ops = (name,name)
name_minmax_ops = (name,name)
< (name,name)
<= (name,name)
> (name,name)
>= (name,name)
numeric_bloom_ops = (numeric,numeric)
numeric_minmax_ops = (numeric,numeric)
< (numeric,numeric)
<= (numeric,numeric)
> (numeric,numeric)
>= (numeric,numeric)
numeric_minmax_multi_ops = (numeric,numeric)
< (numeric,numeric)
<= (numeric,numeric)
> (numeric,numeric)
>= (numeric,numeric)
oid_bloom_ops = (oid,oid)
oid_minmax_ops = (oid,oid)
< (oid,oid)
> (oid,oid)
<= (oid,oid)
>= (oid,oid)
oid_minmax_multi_ops = (oid,oid)
< (oid,oid)
> (oid,oid)
<= (oid,oid)
>= (oid,oid)
pg_lsn_bloom_ops = (pg_lsn,pg_lsn)
pg_lsn_minmax_ops = (pg_lsn,pg_lsn)
< (pg_lsn,pg_lsn)
> (pg_lsn,pg_lsn)
<= (pg_lsn,pg_lsn)
>= (pg_lsn,pg_lsn)
pg_lsn_minmax_multi_ops = (pg_lsn,pg_lsn)
< (pg_lsn,pg_lsn)
> (pg_lsn,pg_lsn)
<= (pg_lsn,pg_lsn)
>= (pg_lsn,pg_lsn)
range_inclusion_ops = (anyrange,anyrange)
< (anyrange,anyrange)
<= (anyrange,anyrange)
>= (anyrange,anyrange)
> (anyrange,anyrange)
&& (anyrange,anyrange)
@> (anyrange,anyelement)
@> (anyrange,anyrange)
<@ (anyrange,anyrange)
<< (anyrange,anyrange)
>> (anyrange,anyrange)
&< (anyrange,anyrange)
&> (anyrange,anyrange)
-|- (anyrange,anyrange)
text_bloom_ops = (text,text)
text_minmax_ops = (text,text)
< (text,text)
<= (text,text)
> (text,text)
>= (text,text)
tid_bloom_ops = (tid,tid)
tid_minmax_ops = (tid,tid)
< (tid,tid)
> (tid,tid)
<= (tid,tid)
>= (tid,tid)
tid_minmax_multi_ops = (tid,tid)
< (tid,tid)
> (tid,tid)
<= (tid,tid)
>= (tid,tid)
timestamp_bloom_ops = (timestamp,timestamp)
timestamp_minmax_ops = (timestamp,timestamp)
< (timestamp,timestamp)
<= (timestamp,timestamp)
> (timestamp,timestamp)
>= (timestamp,timestamp)
timestamp_minmax_multi_ops = (timestamp,timestamp)
< (timestamp,timestamp)
<= (timestamp,timestamp)
> (timestamp,timestamp)
>= (timestamp,timestamp)
timestamptz_bloom_ops = (timestamptz,timestamptz)
timestamptz_minmax_ops = (timestamptz,timestamptz)
< (timestamptz,timestamptz)
<= (timestamptz,timestamptz)
> (timestamptz,timestamptz)
>= (timestamptz,timestamptz)
timestamptz_minmax_multi_ops = (timestamptz,timestamptz)
< (timestamptz,timestamptz)
<= (timestamptz,timestamptz)
> (timestamptz,timestamptz)
>= (timestamptz,timestamptz)
time_bloom_ops = (time,time)
time_minmax_ops = (time,time)
< (time,time)
<= (time,time)
> (time,time)
>= (time,time)
time_minmax_multi_ops = (time,time)
< (time,time)
<= (time,time)
> (time,time)
>= (time,time)
timetz_bloom_ops = (timetz,timetz)
timetz_minmax_ops = (timetz,timetz)
< (timetz,timetz)
<= (timetz,timetz)
> (timetz,timetz)
>= (timetz,timetz)
timetz_minmax_multi_ops = (timetz,timetz)
< (timetz,timetz)
<= (timetz,timetz)
> (timetz,timetz)
>= (timetz,timetz)
uuid_bloom_ops = (uuid,uuid)
uuid_minmax_ops = (uuid,uuid)
< (uuid,uuid)
> (uuid,uuid)
<= (uuid,uuid)
>= (uuid,uuid)
uuid_minmax_multi_ops = (uuid,uuid)
< (uuid,uuid)
> (uuid,uuid)
<= (uuid,uuid)
>= (uuid,uuid)
varbit_minmax_ops = (varbit,varbit)
< (varbit,varbit)
> (varbit,varbit)
<= (varbit,varbit)
>= (varbit,varbit)

65.5.2.1. 運算子類引數 #

一些內建運算子類允許指定影響運算子類行為的引數。每個運算子類都有自己的一組允許引數。只有 bloomminmax-multi 運算子類允許指定引數。

bloom 運算子類接受以下引數:

n_distinct_per_range

定義塊範圍內不同非空值的估計數量,用於BRINbloom 索引為 Bloom 過濾器調整大小。它與 n_distinct 選項類似,用於 ALTER TABLE。當設定為正值時,每個塊範圍假定包含此數量的不同非空值。當設定為負值(必須大於或等於 -1)時,不同非空值的數量假定隨塊範圍內可能的最大元組數量(每塊約 290 行)線性增長。預設值為 -0.1,不同非空值的最小數量為 16

false_positive_rate

定義所需的誤報率,由BRINbloom 索引用於 Bloom 過濾器的大小調整。值必須在 0.0001 和 0.25 之間。預設值為 0.01,即 1% 的誤報率。

minmax-multi 運算子類接受以下引數:

values_per_range

定義用於摘要塊範圍的BRINminmax 索引儲存的最大值數量。每個值可能代表一個點或一個區間的邊界。值必須在 8 到 256 之間,預設值為 32。

65.5.3. 可擴充套件性 #

BRIN介面具有高度抽象性,要求訪問方法實現者只需實現被訪問資料型別的語義。該BRIN層本身負責併發、日誌記錄和搜尋索引結構。

要使BRIN訪問方法正常工作,只需實現幾個使用者定義的函式,這些函式定義索引中儲存的摘要值的行為以及它們與掃描鍵的互動方式。簡而言之,BRIN將可擴充套件性與通用性、程式碼重用和清晰的介面相結合。

運算子類需要為BRIN提供四個方法:

BrinOpcInfo *opcInfo(Oid type_oid)

返回有關被索引列摘要的內部資訊。返回值必須指向一個 palloc'd 的 BrinOpcInfo,其定義如下:

typedef struct BrinOpcInfo
{
    /* Number of columns stored in an index column of this opclass */
    uint16      oi_nstored;

    /* Opaque pointer for the opclass' private use */
    void       *oi_opaque;

    /* Type cache entries of the stored columns */
    TypeCacheEntry *oi_typcache[FLEXIBLE_ARRAY_MEMBER];
} BrinOpcInfo;

BrinOpcInfo.oi_opaque 可由運算子類例程在索引掃描期間在支援函式之間傳遞資訊。

bool consistent(BrinDesc *bdesc, BrinValues *column, ScanKey *keys, int nkeys)

返回給定範圍的索引值是否與所有 ScanKey 條目一致。要使用的屬性號作為掃描鍵的一部分傳遞。可能一次傳遞同一屬性的多個掃描鍵;條目數量由 nkeys 引數確定。

bool consistent(BrinDesc *bdesc, BrinValues *column, ScanKey key)

返回掃描鍵是否與給定範圍的索引值一致。要使用的屬性號作為掃描鍵的一部分傳遞。這是 consistent 函式的一個較舊的向後相容變體。

bool addValue(BrinDesc *bdesc, BrinValues *column, Datum newval, bool isnull)

給定一個索引元組和一個索引值,修改元組的指定屬性,使其另外表示新值。如果對元組進行了任何修改,則返回 true

bool unionTuples(BrinDesc *bdesc, BrinValues *a, BrinValues *b)

合併兩個索引元組。給定兩個索引元組,修改第一個元組的指定屬性,使其表示兩個元組。第二個元組不會被修改。

運算子類可以為BRIN選擇性地指定以下方法:

void options(local_relopts *relopts)

定義一組使用者可見的引數,用於控制運算子類的行為。

options 函式接收一個指向 local_relopts 結構的指標,該結構需要用一組運算子類特定的選項來填充。可以透過 PG_HAS_OPCLASS_OPTIONS()PG_GET_OPCLASS_OPTIONS() 宏從其他支援函式訪問選項。

由於被索引值的鍵提取和鍵在BRIN中的表示都非常靈活,它們可能依賴於使用者指定的引數。

核心發行版支援四種類型的運算子類:minmax、minmax-multi、inclusion 和 bloom。使用這些運算子類的運算子類定義會根據需要提供給核心資料型別。使用者可以透過使用等效定義為其他資料型別定義額外的運算子類,而無需編寫任何原始碼;宣告適當的目錄條目就足夠了。請注意,有關運算子策略的語義的假設已嵌入到支援函式的原始碼中。

實現完全不同語義的運算子類也是可能的,前提是實現了上述四個主要支援函式。請注意,不同主要版本之間的向後相容性不能保證:例如,後續版本可能需要額外的支援函式。

要為實現全序集的某個資料型別編寫運算子類,可以使用 minmax 支援函式以及相應的運算子,如 表 65.5 所示。所有運算子類成員(函式和運算子)都是強制性的。

表 65.5. Minmax 運算子類的函式和支援編號

運算子類成員 物件
支援函式 1 內部函式 brin_minmax_opcinfo()
支援函式 2 內部函式 brin_minmax_add_value()
支援函式 3 內部函式 brin_minmax_consistent()
支援函式 4 內部函式 brin_minmax_union()
運算子策略 1 運算子小於
運算子策略 2 運算子小於或等於
運算子策略 3 運算子等於
運算子策略 4 運算子大於或等於
運算子策略 5 運算子大於

要為包含值在一個型別內的複雜資料型別編寫運算子類,可以使用 inclusion 支援函式以及相應的運算子,如 表 65.6 所示。它只需要一個額外的函式,可以用任何語言編寫。可以定義更多函式來實現附加功能。所有運算子都是可選的。某些運算子需要其他運算子,如表格所示的依賴關係。

表 65.6. Inclusion 運算子類的函式和支援編號

運算子類成員 物件 依賴
支援函式 1 內部函式 brin_inclusion_opcinfo()  
支援函式 2 內部函式 brin_inclusion_add_value()  
支援函式 3 內部函式 brin_inclusion_consistent()  
支援函式 4 內部函式 brin_inclusion_union()  
支援函式 11 合併兩個元素的函式  
支援函式 12 檢查兩個元素是否可合併的可選函式  
支援函式 13 檢查元素是否包含在另一個元素內的可選函式  
支援函式 14 檢查元素是否為空的可選函式  
運算子策略 1 運算子左側 運算子策略 4
運算子策略 2 運算子不向右擴充套件 運算子策略 5
運算子策略 3 運算子重疊  
運算子策略 4 運算子不向左擴充套件 運算子策略 1
運算子策略 5 運算子右側 運算子策略 2
運算子策略 6, 18 運算子相同或等於 運算子策略 7
運算子策略 7, 16, 24, 25 運算子包含或等於  
運算子策略 8, 26, 27 運算子被包含或等於 運算子策略 3
運算子策略 9 運算子不向上擴充套件 運算子策略 11
運算子策略 10 運算子在下方 運算子策略 12
運算子策略 11 運算子在上方 運算子策略 9
運算子策略 12 運算子不向下擴充套件 運算子策略 10
運算子策略 20 運算子小於 運算子策略 5
運算子策略 21 運算子小於或等於 運算子策略 5
運算子策略 22 運算子大於 運算子策略 1
運算子策略 23 運算子大於或等於 運算子策略 1

支援函式編號 1 到 10 保留給 BRIN 內部函式,因此 SQL 級別的函式從編號 11 開始。支援函式編號 11 是構建索引所需的主要函式。它應該接受兩個與運算子類相同資料型別的引數,並返回它們的並集。如果使用了 STORAGE 引數定義了 inclusion 運算子類,則它允許儲存不同資料型別的並集值。並集函式返回值應與 STORAGE 資料型別匹配。

支援函式編號 12 和 14 用於支援內建資料型別的不規則性。函式編號 12 用於支援不同族但不可合併的網路地址。函式編號 14 用於支援空範圍。函式編號 13 是一個可選但推薦的函式,它允許在將新值傳遞給並集函式之前對其進行檢查。由於 BRIN 框架可以在並集未更改時跳過某些操作,因此使用此函式可以提高索引效能。

要為僅實現相等運算子並支援雜湊的資料型別編寫運算子類,可以使用 bloom 支援過程以及相應的運算子,如 表 65.7 所示。所有運算子類成員(過程和運算子)都是強制性的。

表 65.7. Bloom 運算子類的過程和支援編號

運算子類成員 物件
支援過程 1 內部函式 brin_bloom_opcinfo()
支援過程 2 內部函式 brin_bloom_add_value()
支援過程 3 內部函式 brin_bloom_consistent()
支援過程 4 內部函式 brin_bloom_union()
支援過程 5 內部函式 brin_bloom_options()
支援過程 11 計算元素雜湊的函式
運算子策略 1 運算子等於

支援過程編號 1-10 保留給 BRIN 內部函式,因此 SQL 級別的函式從編號 11 開始。支援過程編號 11 是構建索引所需的主要函式。它應該接受一個與運算子類相同資料型別的引數,並返回該值的雜湊值。

minmax-multi 運算子類也用於實現全序集的資料型別,可以看作是 minmax 運算子類的一個簡單擴充套件。雖然 minmax 運算子類將每個塊範圍的值摘要為一個連續區間,但 minmax-multi 允許摘要為多個較小的區間以改善異常值處理。可以使用 minmax-multi 支援過程以及相應的運算子,如 表 65.8 所示。所有運算子類成員(過程和運算子)都是強制性的。

表 65.8. minmax-multi 運算子類的過程和支援編號

運算子類成員 物件
支援過程 1 內部函式 brin_minmax_multi_opcinfo()
支援過程 2 內部函式 brin_minmax_multi_add_value()
支援過程 3 內部函式 brin_minmax_multi_consistent()
支援過程 4 內部函式 brin_minmax_multi_union()
支援過程 5 內部函式 brin_minmax_multi_options()
支援過程 11 計算兩個值之間距離(區間長度)的函式
運算子策略 1 運算子小於
運算子策略 2 運算子小於或等於
運算子策略 3 運算子等於
運算子策略 4 運算子大於或等於
運算子策略 5 運算子大於

minmax 和 inclusion 運算子類都支援跨資料型別的運算子,儘管使用這些運算子會使依賴關係變得更加複雜。minmax 運算子類需要為兩個引數都具有相同資料型別的一整套運算子。它允許透過定義額外的運算子集來支援其他資料型別。inclusion 運算子類的運算子策略依賴於另一個運算子策略,如 表 65.6 所示,或者依賴於自身的運算子策略。它們要求依賴項運算子使用 STORAGE 資料型別作為運算子的左側引數,而另一個支援的資料型別作為右側引數。以 float4_minmax_ops 為 minmax 的示例,以 box_inclusion_ops 為 inclusion 的示例。

提交更正

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