2024 年 9 月 26 日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本:11 / 10 / 9.6 / 9.5

64.5. BRIN 索引 #

64.5.1. 简介 #

BRIN代表块范围索引。BRIN旨在处理非常大的表,其中某些列与其在表中的物理位置具有一定的自然相关性。

BRIN块范围(或页范围)为单位工作。块范围是一组在表中物理相邻的页;对于每个块范围,索引会存储一些摘要信息。例如,存储商店销售订单的表可能有一个日期列,记录每个订单放置的日期,并且大多数情况下,较早订单的条目也会更早出现在表中;存储邮政编码列的表可能所有城市的邮政编码自然地分组在一起。

BRIN索引可以通过常规位图索引扫描来满足查询,并且如果索引存储的摘要信息与查询条件一致,则将返回每个范围内的所有页面的所有元组。查询执行器负责重新检查这些元组并丢弃不匹配查询条件的元组——换句话说,这些索引是有损的。由于一个BRIN索引非常小,扫描索引与顺序扫描相比几乎没有额外开销,但可以避免扫描已知不包含匹配元组的表的大部分。

具体数据BRIN索引将存储,以及索引能够满足的特定查询,取决于为索引的每一列选择的运算符类。具有线性排序顺序的数据类型可以具有存储每个块范围内最小值和最大值的运算符类,例如;几何类型可能会存储块范围内所有对象的边界框。

块范围的大小在索引创建时由pages_per_range存储参数确定。索引条目的数量将等于关系大小(以页为单位)除以pages_per_range的选定值。因此,数字越小,索引越大(因为需要存储更多索引条目),但同时存储的摘要数据可以更精确,并且可以在索引扫描期间跳过更多数据块。

64.5.1.1. 索引维护 #

在创建时,会扫描所有现有的堆页,并为每个范围创建摘要索引元组,包括结尾处可能不完整的范围。随着新页填充数据,已进行摘要的页范围将导致摘要信息更新为来自新元组的数据。当创建一个不属于最后一个已摘要范围的新页时,新页所属的范围不会自动获得摘要元组;这些元组将保持未摘要状态,直到稍后调用摘要运行,为该范围创建初始摘要。

有几种方法可以触发页范围的初始摘要。如果对表进行真空操作,无论是手动还是通过自动真空,所有现有的未摘要页范围都会被摘要。此外,如果索引的autosummarize参数已启用(默认情况下未启用),则无论表本身是否由自动真空处理,只要该数据库中运行自动真空,就会对所有已填充的未摘要页范围进行摘要;见下文。

最后,可以使用以下函数(在这些函数运行时,search_path 会暂时更改为pg_catalog, pg_temp

brin_summarize_new_values(regclass) 对所有未摘要范围进行摘要;
brin_summarize_range(regclass, bigint) 只对包含给定页面的范围进行摘要(如果该范围未进行摘要)。

当启用自动摘要时,当检测到下一个块范围的第一个页面的第一个条目的插入时,会向autovacuum发送一个请求,以对块范围执行目标摘要,并在同一个数据库中的自动真空工作器下次完成运行时执行。如果请求队列已满,则不会记录请求,并且会向服务器日志发送一条消息

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

发生这种情况时,该范围将保持未摘要状态,直到下次对表进行定期真空操作,或调用上述函数之一。

相反,可以使用brin_desummarize_range(regclass, bigint) 函数对范围进行去摘要,当索引元组不再是最佳表示(因为现有值已更改)时,此函数非常有用。有关详细信息,请参见第 9.28.8 节

64.5.2. 内置操作符类 #

核心 PostgreSQL 分发版包含BRIN运算符类,如表 64.4 所示。

minmax 运算符类存储范围中索引列中出现的最小值和最大值。inclusion 运算符类存储一个值,该值包括范围中索引列中的值。bloom 运算符类为范围中的所有值构建一个布隆过滤器。minmax-multi 运算符类存储多个最小值和最大值,表示范围中索引列中出现的多个值。

表 64.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)

64.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。

64.5.3. 可扩展性 #

BRIN接口具有很高的抽象级别,仅要求访问方法实现者实现正在访问的数据类型的语义。该BRIN层本身负责并发、日志记录和搜索索引结构。

让一个BRIN访问方法工作,只需实现一些用户定义的方法,这些方法定义了存储在索引中的摘要值的特性,以及它们与扫描键的交互方式。简而言之,BRIN将可扩展性与通用性、代码重用和干净的接口结合起来。

有四种方法,操作符类对于BRIN必须提供

BrinOpcInfo *opcInfo(Oid type_oid)

返回有关索引列的摘要数据的内部信息。返回值必须指向一个通过 palloc 分配的 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)

返回 ScanKey 是否与给定范围的索引值一致。要使用的属性号作为扫描键的一部分传递。这是 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、包含和 bloom。针对核心数据类型定义的基于这些操作符类的定义被适当地运送。用户可以使用等效的定义为其他数据类型定义额外的操作符类,而无需编写任何源代码;声明适当的目录条目就足够了。请注意,关于操作符策略语义的假设嵌入在支持函数的源代码中。

也可以定义实现完全不同语义的操作符类,前提是编写上面描述的四个主要支持函数的实现。请注意,跨主要版本的向后兼容性不受保证:例如,在以后的版本中可能需要额外的支持函数。

要为实现完全有序集的数据类型编写操作符类,可以使用 minmax 支持函数以及相应的操作符,如 表 64.5 所示。所有操作符类成员(函数和操作符)都是强制性的。

表 64.5. Minmax 操作符类的函数和支持编号

操作符类成员 对象
支持函数 1 内部函数 brin_minmax_opcinfo()
支持函数 2 内部函数 brin_minmax_add_value()
支持函数 3 内部函数 brin_minmax_consistent()
支持函数 4 内部函数 brin_minmax_union()
操作符策略 1 小于操作符
操作符策略 2 小于或等于操作符
操作符策略 3 等于操作符
操作符策略 4 大于或等于操作符
操作符策略 5 大于操作符

要为具有包含在另一种类型中的值的复杂数据类型编写操作符类,可以使用包含支持函数以及相应的操作符,如 表 64.6 所示。它只需要一个额外的函数,该函数可以用任何语言编写。可以为额外功能定义更多函数。所有操作符都是可选的。一些操作符需要其他操作符,如表中的依赖项所示。

表 64.6. 包含操作符类的函数和支持编号

操作符类成员 对象 依赖项
支持函数 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 是构建索引所需的 principal 函数。它应该接受两个与操作符类相同数据类型的参数,并返回它们的并集。如果包含操作符类使用 STORAGE 参数定义,则它可以存储具有不同数据类型的并集值。并集函数的返回值应该与 STORAGE 数据类型匹配。

支持函数编号 12 和 14 用于支持内置数据类型的异常情况。函数编号 12 用于支持来自不同族且不可合并的网络地址。函数编号 14 用于支持空范围。函数编号 13 是可选但推荐的函数,它允许在将新值传递给并集函数之前对其进行检查。由于 BRIN 框架可以在并集未改变时对某些操作进行简化,因此使用此函数可以提高索引性能。

要为仅实现相等操作符并支持散列的数据类型编写操作符类,可以使用 bloom 支持过程以及相应的操作符,如 表 64.7 所示。所有操作符类成员(过程和操作符)都是强制性的。

表 64.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 是构建索引所需的 principal 函数。它应该接受一个与操作符类相同数据类型的参数,并返回值的散列值。

minmax-multi 操作符类也适用于实现完全有序集的数据类型,可以看作是 minmax 操作符类的简单扩展。虽然 minmax 操作符类将每个块范围的值汇总成一个连续的区间,但 minmax-multi 允许将值汇总成多个更小的区间,以改进对异常值的处理。可以使用 minmax-multi 支持过程以及相应的操作符,如 表 64.8 所示。所有操作符类成员(过程和操作符)都是强制性的。

表 64.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 和包含运算符类都支持跨数据类型运算符,但这些运算符的依赖关系更复杂。minmax 运算符类要求定义一组完整的运算符,其中两个参数具有相同的类型。它允许通过定义额外的运算符集来支持更多数据类型。包含运算符类的运算符策略取决于另一个运算符策略,如 表 64.6 所示,或与自身相同的运算符策略。它们要求将依赖运算符定义为使用 STORAGE 数据类型作为左侧参数,并将另一个支持的数据类型作为支持运算符的右侧参数。例如,float4_minmax_ops 是 minmax 的示例,box_inclusion_ops 是包含的示例。

提交更正

如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表格 报告文档问题。