受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

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. 索引维护 #

在创建索引时,会扫描所有现有堆页,并为每个范围创建一个摘要索引元组,末尾那个可能不完整的范围也包括在内。 随着新页被数据填满,已经完成摘要的页范围会利用新元组中的数据更新其摘要信息。 当创建了一个不属于最后一个已摘要范围的新页时,该新页所属的范围不会自动获得摘要元组; 这些元组会一直保持未摘要状态,直到稍后调用摘要操作,为该范围创建初始摘要。

有几种方式可以触发页范围的初始摘要。 如果对该表执行了清理,无论是手动执行还是由 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) 函数对某个范围取消摘要;当现有值已经发生变化,以致索引元组不再是良好表示时,这会很有用。 详见 Section 9.28.8

65.5.2. 内置操作符类 #

核心 PostgreSQL 发行版包含 Table 65.4 中所示的 BRIN 操作符类。

minmax 操作符类存储该范围内索引列中出现的最小值和最大值。 inclusion 操作符类存储一个能够包含该范围内索引列值的值。 bloom 操作符类会为该范围内的全部值构建一个布隆过滤器。 minmax-multi 操作符类则存储多个最小值和最大值,以表示该范围内索引列中出现的值。

Table 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

定义块范围内非空不同值的估计数量,BRIN bloom 索引用它来确定布隆过滤器的大小。 其行为类似于 ALTER TABLEn_distinct 选项。 设置为正值时,假定每个块范围都包含这么多个非空不同值。 设置为负值时(必须大于或等于 -1),假定非空不同值的数量会随着块范围中元组最大可能数量线性增长(每个块约 290 行)。 默认值为 -0.1,非空不同值的最小数量为 16

false_positive_rate

定义 BRIN bloom 索引用于确定布隆过滤器大小的目标误报率。 取值必须在 0.0001 到 0.25 之间。默认值为 0.01,即 1% 的误报率。

minmax-multi 操作符类接受这些参数:

values_per_range

定义 BRIN minmax-multi 索引为摘要一个块范围而存储的最大值个数。 每个值可以表示一个点,也可以表示一个区间的边界。 取值必须在 8 到 256 之间,默认值为 32。

65.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、inclusion 和 bloom。 针对核心数据类型,发行版会酌情附带使用它们的操作符类定义。 用户也可以为其他数据类型定义等价的操作符类,而无需编写任何源代码;只要声明适当的系统目录项即可。 注意,对操作符策略语义的某些假设嵌入在支持函数的源码中。

只要为上文所述的四个主要支持函数编写实现,也可以实现语义完全不同的操作符类。 注意,不保证跨主版本的向后兼容性:例如,在后续版本中可能需要附加的支持函数。

要为实现全序集的数据类型编写操作符类,可以按 Table 65.5 所示,将 minmax 支持函数与相应操作符一起使用。 所有操作符类成员(函数和操作符)都是必需的。

Table 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 大于操作符

要为一种值能够被包含在另一种类型中的复杂数据类型编写操作符类, 可以按 Table 65.6 所示, 将 inclusion 支持函数与相应操作符一起使用。 它只需要额外一个函数,而且该函数可以用任何语言编写。 还可以定义更多函数以提供附加功能。 所有操作符都是可选的。某些操作符依赖其他操作符,表中已列出这些依赖关系。

Table 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 left-of 操作符 操作符策略 4
操作符策略 2 does-not-extend-to-the-right-of 操作符 操作符策略 5
操作符策略 3 overlaps 操作符  
操作符策略 4 does-not-extend-to-the-left-of 操作符 操作符策略 1
操作符策略 5 right-of 操作符 操作符策略 2
操作符策略 6, 18 same-as-or-equal-to 操作符 操作符策略 7
操作符策略 7, 16, 24, 25 contains-or-equal-to 操作符  
操作符策略 8, 26, 27 is-contained-by-or-equal-to 操作符 操作符策略 3
操作符策略 9 does-not-extend-above 操作符 操作符策略 11
操作符策略 10 is-below 操作符 操作符策略 12
操作符策略 11 is-above 操作符 操作符策略 9
操作符策略 12 does-not-extend-below 操作符 操作符策略 10
操作符策略 20 小于操作符 操作符策略 5
操作符策略 21 小于等于操作符 操作符策略 5
操作符策略 22 大于操作符 操作符策略 1
操作符策略 23 大于等于操作符 操作符策略 1

支持函数编号 1 到 10 保留给 BRIN 内部函数,因此 SQL 层函数从编号 11 开始。 支持函数 11 是构建索引所需的主要函数。 它应接受两个与操作符类数据类型相同的参数,并返回它们的并集。 如果 inclusion 操作符类在定义时使用了 STORAGE 参数, 则它可以存储具有不同数据类型的并集值。 并集函数的返回值应与 STORAGE 数据类型匹配。

支持函数 12 和 14 用于处理内置数据类型中的不规则情况。 函数 12 用于支持来自不同地址族、无法合并的网络地址。 函数 14 用于支持空范围。 函数 13 是可选但推荐的;它允许在把新值传给并集函数之前先进行检查。 由于 BRIN 框架在并集未发生变化时可以跳过某些操作,使用这个函数可以提升索引性能。

要为仅实现等值操作符且支持哈希的数据类型编写操作符类,可以按 Table 65.7 所示,将 bloom 支持过程与相应操作符一起使用。 所有操作符类成员(过程和操作符)都是必需的。

Table 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 则允许将其摘要为多个较小区间,以改进对离群值的处理。 可以按 Table 65.8 所示,将 minmax-multi 支持过程与相应操作符一起使用。 所有操作符类成员(过程和操作符)都是必需的。

Table 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 操作符类要求定义一整套两侧参数都具有相同数据类型的操作符。 它还允许通过定义额外的操作符集合来支持附加数据类型。 如 Table 65.6 所示, inclusion 操作符类的操作符策略依赖于另一种操作符策略,或者依赖于它们自身对应的同名操作符策略。 这要求把依赖操作符定义为:左侧参数是 STORAGE 数据类型, 右侧参数是其他受支持的数据类型。 minmax 的示例见 float4_minmax_ops,inclusion 的示例见 box_inclusion_ops

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。