bloom
提供了一个基于 布隆过滤器 的索引访问方法。
布隆过滤器是一种节省空间的数据结构,用于测试一个元素是否属于一个集合。在索引访问方法的场景下,它允许通过在索引创建时确定大小的签名(signatures)来快速排除不匹配的行(tuples)。
签名是对被索引列(或列组合)的一种有损表示,因此容易报告假阳性(false positives);也就是说,它可能报告一个元素存在于集合中,而实际上并不存在。因此,索引搜索结果必须始终使用堆(heap)条目中的实际列值进行重新检查。更大的签名可以降低假阳性的几率,从而减少无用的堆访问次数,但当然也会使索引更大,扫描起来更慢。
当一个表有很多列,并且查询测试这些列的任意组合时,这种类型的索引最为有用。传统的 btree 索引比 bloom 索引快,但可能需要很多 btree 索引才能支持所有可能的查询,而一个 bloom 索引就足够了。但请注意,bloom 索引仅支持等值查询,而 btree 索引还可以执行不等值和范围搜索。
一个 bloom
索引在其 WITH
子句中接受以下参数:
length
每个签名(索引条目)的长度,以位(bits)为单位。它会向上舍入到最近的 16
的倍数。默认值为 80
位,最大值为 4096
位。
col1 — col32
为每个索引列生成的位数。每个参数的名称指的是它所控制的索引列的编号。默认值为 2
位,最大值为 4095
位。对于未实际使用的索引列的参数将被忽略。
这是一个创建 bloom 索引的示例:
CREATE INDEX bloomidx ON tbloom USING bloom (i1,i2,i3) WITH (length=80, col1=2, col2=2, col3=4);
该索引的创建签名长度为 80 位,其中列 i1 和 i2 映射到 2 位,列 i3 映射到 4 位。由于 length
、col1
和 col2
的值是默认值,因此可以省略它们的说明。
以下是一个更完整的 bloom 索引定义和使用示例,以及与等效 btree 索引的比较。bloom 索引比 btree 索引小得多,并且性能可能更好。
=# CREATE TABLE tbloom AS SELECT (random() * 1000000)::int as i1, (random() * 1000000)::int as i2, (random() * 1000000)::int as i3, (random() * 1000000)::int as i4, (random() * 1000000)::int as i5, (random() * 1000000)::int as i6 FROM generate_series(1,10000000); SELECT 10000000
对这个大表进行顺序扫描需要很长时间。
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451; QUERY PLAN ------------------------------------------------------------------------------------------------------ Seq Scan on tbloom (cost=0.00..213744.00 rows=250 width=24) (actual time=357.059..357.059 rows=0.00 loops=1) Filter: ((i2 = 898732) AND (i5 = 123451)) Rows Removed by Filter: 10000000 Buffers: shared hit=63744 Planning Time: 0.346 ms Execution Time: 357.076 ms (6 rows)
即使定义了 btree 索引,结果仍然是顺序扫描。
=# CREATE INDEX btreeidx ON tbloom (i1, i2, i3, i4, i5, i6); CREATE INDEX =# SELECT pg_size_pretty(pg_relation_size('btreeidx')); pg_size_pretty ---------------- 386 MB (1 row) =# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451; QUERY PLAN ------------------------------------------------------------------------------------------------------ Seq Scan on tbloom (cost=0.00..213744.00 rows=2 width=24) (actual time=351.016..351.017 rows=0.00 loops=1) Filter: ((i2 = 898732) AND (i5 = 123451)) Rows Removed by Filter: 10000000 Buffers: shared hit=63744 Planning Time: 0.138 ms Execution Time: 351.035 ms (6 rows)
在表上定义 bloom 索引比 btree 在处理这类搜索时表现更好。
=# CREATE INDEX bloomidx ON tbloom USING bloom (i1, i2, i3, i4, i5, i6); CREATE INDEX =# SELECT pg_size_pretty(pg_relation_size('bloomidx')); pg_size_pretty ---------------- 153 MB (1 row) =# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on tbloom (cost=1792.00..1799.69 rows=2 width=24) (actual time=22.605..22.606 rows=0.00 loops=1) Recheck Cond: ((i2 = 898732) AND (i5 = 123451)) Rows Removed by Index Recheck: 2300 Heap Blocks: exact=2256 Buffers: shared hit=21864 -> Bitmap Index Scan on bloomidx (cost=0.00..178436.00 rows=1 width=0) (actual time=20.005..20.005 rows=2300.00 loops=1) Index Cond: ((i2 = 898732) AND (i5 = 123451)) Index Searches: 1 Buffers: shared hit=19608 Planning Time: 0.099 ms Execution Time: 22.632 ms (11 rows)
现在,btree 搜索的主要问题是,当搜索条件不限制最左边的索引列时,btree 效率低下。对于 btree 更好的策略是为每列创建一个单独的索引。然后规划器将选择类似以下的查询:
=# CREATE INDEX btreeidx1 ON tbloom (i1); CREATE INDEX =# CREATE INDEX btreeidx2 ON tbloom (i2); CREATE INDEX =# CREATE INDEX btreeidx3 ON tbloom (i3); CREATE INDEX =# CREATE INDEX btreeidx4 ON tbloom (i4); CREATE INDEX =# CREATE INDEX btreeidx5 ON tbloom (i5); CREATE INDEX =# CREATE INDEX btreeidx6 ON tbloom (i6); CREATE INDEX =# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on tbloom (cost=9.29..13.30 rows=1 width=24) (actual time=0.032..0.033 rows=0.00 loops=1) Recheck Cond: ((i5 = 123451) AND (i2 = 898732)) Buffers: shared read=6 -> BitmapAnd (cost=9.29..9.29 rows=1 width=0) (actual time=0.047..0.047 rows=0.00 loops=1) Buffers: shared hit=6 -> Bitmap Index Scan on btreeidx5 (cost=0.00..4.52 rows=11 width=0) (actual time=0.026..0.026 rows=7.00 loops=1) Index Cond: (i5 = 123451) Index Searches: 1 Buffers: shared hit=3 -> Bitmap Index Scan on btreeidx2 (cost=0.00..4.52 rows=11 width=0) (actual time=0.007..0.007 rows=8.00 loops=1) Index Cond: (i2 = 898732) Index Searches: 1 Buffers: shared hit=3 Planning Time: 0.264 ms Execution Time: 0.047 ms (15 rows)
尽管这个查询比使用任何单个索引都运行得快得多,但我们在索引大小上付出了代价。每个单列 btree 索引占用 88.5 MB,因此总空间需求为 531 MB,是 bloom 索引所用空间的三倍多。
bloom 索引的操作符类仅需要索引数据类型的哈希函数和搜索的相等性操作符。本示例展示了 text
数据类型的操作符类定义:
CREATE OPERATOR CLASS text_ops DEFAULT FOR TYPE text USING bloom AS OPERATOR 1 =(text, text), FUNCTION 1 hashtext(text);
该模块仅包含 int4
和 text
数据类型的操作符类。
仅支持 =
操作符进行搜索。但未来有可能为数组添加支持,支持联合(union)和交集(intersection)操作。
bloom
访问方法不支持 UNIQUE
索引。
bloom
访问方法不支持搜索 NULL
值。
Teodor Sigaev <teodor@postgrespro.ru>
,Postgres Professional,莫斯科,俄罗斯
Alexander Korotkov <a.korotkov@postgrespro.ru>
,Postgres Professional,莫斯科,俄罗斯
Oleg Bartunov <obartunov@postgrespro.ru>
,Postgres Professional,莫斯科,俄罗斯
如果您在文档中看到任何不正确、与您对特定功能的经验不符或需要进一步澄清的内容,请使用 此表格 报告文档问题。