BRIN 索引
当表非常大,且某些列的值与其物理存储位置存在天然关联时,可以使用块范围索引(BRIN)。BRIN 按块范围(或页面范围)进行索引,这些范围是表中物理上相邻的一组页面。BRIN 为每个块范围存储摘要信息。例如,一个存储订单数据的表中,订单日期较早的记录往往靠前;又如,存储邮政编码的表中,相同城市的编码可能被自然聚集在一起。
BRIN 索引可通过位图索引扫描参与查询。当索引中存储的摘要信息与查询条件匹配时,查询会访问该范围内的所有页面,并返回其中的所有元组。查询执行器会逐一验证这些元组,仅保留满足条件的结果,因此 BRIN 是一种“有损”索引。由于 BRIN 索引本身非常小,因此与顺序扫描相比,其额外开销很低,同时能跳过大量不相关的数据块。
BRIN 索引存储哪些数据、能加速哪些查询,取决于为每列选择的操作符类。对于具有线性排序的列,索引会记录每个块范围内的最小值和最大值;而几何类型则记录块范围内所有对象的边界框。
块范围的大小由创建索引时的 pages_per_range
参数决定。索引中条目的数量等于表的页面总数除以 pages_per_range
值。该值越小,索引越大(因为条目更多),但摘要信息也越精细,扫描时可跳过更多不相关的数据块。pages_per_range
默认值为:堆表为 32
,追加优化表为 1
。
从 v2.0.0 起,BRIN 索引支持在 AO 表和 CO 表上创建,并通过链式 revmap 结构优化存储,降低空间占用。该优化提升了存储结构与摘要维护的效率,同时支持通过 brin_summarize_range()
手动生成摘要。
虽然在 AO/CO 表上可以创建并维护 BRIN 索引,但是否会被优化器使用,还取决于查询条件、数据分布、页面数量以及启发式成本模型。因此,并非所有使用 BRIN 的场景都能自动获得性能收益,建议在数据量大、数据具有良好聚簇性的场景中使用。
维护 BRIN 索引
在索引创建时,Apache Cloudberry 会扫描已有堆页面,并为每个范围生成摘要索引元组,包括可能不完整的尾部范围。当数据写入填满新页面时,系统会尝试更新已计算范围的摘要信息。但对于新页面所在的范围,如果其尚未被计算,系统不会自动创建摘要元组。这些范围会处于“未计算”状态,需通过手动方式触发摘要生成。
以下方式可用于生成初始摘要:
- 执行
VACUUM
操作时,系统会自动计算所有尚未处理的页面范围。
也可以使用以下函数手动控制:
brin_summarize_new_values(regclass)
:计算所有尚未处理的范围;brin_summarize_range(regclass, bigint)
:只计算包含指定页面的范围(若尚未计算)。
相反,如需撤销摘要信息,可使用 brin_desummarize_range(regclass, bigint)
函数取消已计算的范围。当数据发生变更,原有摘要信息不再准确时,该操作尤为有用。
下表列出了可用于 BRIN 索引维护的函数。它们不能在恢复模式下执行,仅限超级用户或索引所有者调用。
名称 | 返回类型 | 描述 |
---|---|---|
brin_summarize_new_values(index regclass) | integer | 计算所有未被处理的页面范围。 |
brin_summarize_range(index regclass, blockNumber bigint) | integer | 若指定块所在范围未被计算,则对其生成摘要。 |
brin_desummarize_range(index regclass, blockNumber bigint) | integer | 若指定块所在范围已被计算,则撤销其摘要信息。 |
brin_summarize_new_values
接受索引名称或 OID,自动查找尚未被计算的范围,并扫描对应表页面生成新的摘要索引元组。它返回生成的摘要条目数量。brin_summarize_range
则只处理包含指定块号的那一个范围。
- 通常情况下,在执行
VACUUM
之后,brin_summarize_new_values()
不会产生新的摘 要,因为默认行为是在清理过程中自动处理所有新增页面范围。该函数主要用于手动摘要刷新,适用于未开启自动清理或写入频繁的场景。 - 若表的页数较少或查询条件选择性不足,优化器可能仍不会选用 BRIN 索引路径。
brin_summarize_range
和brin_desummarize_range
仅对实际存在的范围生效。若提供了不存在的块号,函数会返回0
。由于数据分布不均,某些范围可能只存在于部分 Segment 上,函数将返回成功处理该范围的 Segment 数量。