PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

65.5. BRIN 索引 #

65.5.1. 简介
65.5.2. 内置操作符类
65.5.3. 可扩展性

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

发生这种情况时,该范围将保持未汇总状态,直到对该表的下一次常规清理 运行,或者上述函数之一被调用。

反过来,可以使用 brin_desummarize_range(regclass, bigint) 函数取消一个范围的汇总,当索引元组因现有值发生变化而不再能很好地 表达数据时,这会很有用。 详情参见 Section 9.28.8。

65.5.2. 内置操作符类 #

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

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

Table 65.4. 内置 BRIN 操作符类

NameIndexable Operators
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. 操作符类参数 #

一些内建操作符类允许指定影响操作符类行为的参数。每个操作符类都有 自己的一组允许的参数。只有 bloom 和 minmax-multi 操作符类允许指定参数:

bloom 操作符类接受以下参数:

n_distinct_per_range

定义块范围内不同非空值的估计数量,供 BRIN bloom 索引用于确定布隆过滤器的大小。其行为与 ALTER TABLE 的 n_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 索引为汇总一个块范围而存储的值的 最大数量。每个值可以表示一个点,或一个区间的边界。取值必须介于 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)

返回该 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 操作符类的函数与支持函数编号

Operator class memberObject
Support Function 1internal function brin_minmax_opcinfo()
Support Function 2internal function brin_minmax_add_value()
Support Function 3internal function brin_minmax_consistent()
Support Function 4internal function brin_minmax_union()
Operator Strategy 1operator less-than
Operator Strategy 2operator less-than-or-equal-to
Operator Strategy 3operator equal-to
Operator Strategy 4operator greater-than-or-equal-to
Operator Strategy 5operator greater-than

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

Table 65.6. Inclusion 操作符类的函数与支持函数编号

Operator class memberObjectDependency
Support Function 1internal function brin_inclusion_opcinfo() 
Support Function 2internal function brin_inclusion_add_value() 
Support Function 3internal function brin_inclusion_consistent() 
Support Function 4internal function brin_inclusion_union() 
Support Function 11合并两个元素的函数 
Support Function 12用于检查两个元素是否可合并的可选函数 
Support Function 13用于检查一个元素是否被包含在另一个元素之内的可选函数 
Support Function 14用于检查一个元素是否为空的可选函数 
Operator Strategy 1operator left-ofOperator Strategy 4
Operator Strategy 2operator does-not-extend-to-the-right-ofOperator Strategy 5
Operator Strategy 3operator overlaps 
Operator Strategy 4operator does-not-extend-to-the-left-ofOperator Strategy 1
Operator Strategy 5operator right-ofOperator Strategy 2
Operator Strategy 6, 18operator same-as-or-equal-toOperator Strategy 7
Operator Strategy 7, 16, 24, 25operator contains-or-equal-to 
Operator Strategy 8, 26, 27operator is-contained-by-or-equal-toOperator Strategy 3
Operator Strategy 9operator does-not-extend-aboveOperator Strategy 11
Operator Strategy 10operator is-belowOperator Strategy 12
Operator Strategy 11operator is-aboveOperator Strategy 9
Operator Strategy 12operator does-not-extend-belowOperator Strategy 10
Operator Strategy 20operator less-thanOperator Strategy 5
Operator Strategy 21operator less-than-or-equal-toOperator Strategy 5
Operator Strategy 22operator greater-thanOperator Strategy 1
Operator Strategy 23operator greater-than-or-equal-toOperator Strategy 1

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

支持函数编号 12 和 14 用于支持内建数据类型的特殊之处。函数编号 12 用于支持来自不同地址族、无法合并的网络地址。函数编号 14 用于支持 空范围。函数编号 13 是可选但推荐的函数,它允许在新值被传入并集函数 之前对其进行检查。由于 BRIN 框架可以在并集未发生变化时跳过某些 操作,使用该函数可以提高索引性能。

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

Table 65.7. Bloom 操作符类的过程与支持函数编号

Operator class memberObject
Support Procedure 1internal function brin_bloom_opcinfo()
Support Procedure 2internal function brin_bloom_add_value()
Support Procedure 3internal function brin_bloom_consistent()
Support Procedure 4internal function brin_bloom_union()
Support Procedure 5internal function brin_bloom_options()
Support Procedure 11计算一个元素的哈希值的函数
Operator Strategy 1operator equal-to

支持过程编号 1-10 被保留给 BRIN 内部函数,因此 SQL 层面的函数从 编号 11 开始。支持函数编号 11 是构建索引所需的主要函数。 它应接受一个与操作符类相同数据类型的参数,并返回该值的哈希。

minmax-multi 操作符类同样面向实现全序集合的数据类型,可以看作 minmax 操作符类的简单扩展。minmax 操作符类把每个块范围内的值汇总为一个连续 区间,而 minmax-multi 允许汇总成多个较小的区间,以改进对离群值的 处理。可以按 Table 65.8 所示,将 minmax-multi 支持过程与相应的操作符配合使用。 所有操作符类成员(过程和操作符)都是必需的。

Table 65.8. minmax-multi 操作符类的过程与支持函数编号

Operator class memberObject
Support Procedure 1internal function brin_minmax_multi_opcinfo()
Support Procedure 2internal function brin_minmax_multi_add_value()
Support Procedure 3internal function brin_minmax_multi_consistent()
Support Procedure 4internal function brin_minmax_multi_union()
Support Procedure 5internal function brin_minmax_multi_options()
Support Procedure 11计算两个值之间的距离(范围的长度)的函数
Operator Strategy 1operator less-than
Operator Strategy 2operator less-than-or-equal-to
Operator Strategy 3operator equal-to
Operator Strategy 4operator greater-than-or-equal-to
Operator Strategy 5operator greater-than

minmax 和 inclusion 操作符类都支持跨数据类型的操作符,不过此时依赖 关系会变得更复杂。minmax 操作符类要求定义一套完整的操作符,其两个 参数具有相同的数据类型。它允许通过定义额外的操作符集合来支持更多 的数据类型。inclusion 操作符类的操作符策略依赖于 Table 65.6 所示的另一个 操作符策略,或与其自身相同的操作符策略。它们要求依赖操作符以 STORAGE 数据类型作为左参数,以被支持操作符的 另一个受支持数据类型作为右参数来定义。参见 float4_minmax_ops 作为 minmax 的例子,以及 box_inclusion_ops 作为 inclusion 的例子。