bloom提供了基于布隆过滤器的索引访问方法。
布隆过滤器是一种空间效率很高的数据结构,用于测试某个元素是否属于某个集合。对于索引访问方法,它允许通过签名快速排除不匹配的元组,而签名的大小则在创建索引时确定。
签名是被索引属性的一种有损表示,因此容易产生误报;也就是说,某个元素实际上并不在集合中,却可能被报告为在集合中。因此,索引搜索结果始终都必须使用堆条目中的实际属性值再次检查。更长的签名可以降低误报概率,从而减少无用的堆访问次数,但当然也会使索引变得更大,因此扫描速度更慢。
当一张表具有很多属性,而查询又会测试这些属性的任意组合时,这类索引最有用。传统 B-树索引比布隆索引更快,但为了支持所有可能的查询,可能需要许多 B-树索引,而布隆索引只需一个。不过要注意,布隆索引只支持等值查询,而 B-树索引还可以执行不等和范围搜索。
bloom索引在其WITH子句中接受下列参数:
length按位计的每个签名(索引项)长度。该值会向上圆整为16的倍数。默认值为80位,最大值为4096。
col1 — col32为每个索引列生成的位数。每个参数的名字都对应它所控制的索引列编号。默认值为2位,最大值为4095。对于实际未使用的索引列,其参数会被忽略。
下面是一个创建布隆索引的示例:
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都采用默认值,因此这些参数指定也可以省略。
下面给出一个更完整的布隆索引定义和使用示例,同时与等效的 B-树索引进行比较。布隆索引明显比 B-树索引更小,而且性能可能更好。
=# 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;
EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
QUERY PLAN
------------------------------------------------------------------------------------------------------
Seq Scan on tbloom (cost=0.00..2137.14 rows=3 width=24) (actual time=14.894..14.895 rows=0 loops=1)
Filter: ((i2 = 898732) AND (i5 = 123451))
Rows Removed by Filter: 100000
Planning Time: 0.350 ms
Execution Time: 14.916 ms
(5 rows)
即使定义了 B-树索引,结果仍然是顺序扫描:
=# CREATE INDEX btreeidx ON tbloom (i1, i2, i3, i4, i5, i6);
CREATE INDEX
=# SELECT pg_size_pretty(pg_relation_size('btreeidx'));
pg_size_pretty
----------------
3992 kB
(1 row)
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
QUERY PLAN
------------------------------------------------------------------------------------------------------
Seq Scan on tbloom (cost=0.00..2137.00 rows=2 width=24) (actual time=12.004..12.004 rows=0 loops=1)
Filter: ((i2 = 898732) AND (i5 = 123451))
Rows Removed by Filter: 100000
Planning Time: 0.157 ms
Execution Time: 12.019 ms
(5 rows)
在表上定义 bloom 索引后,处理此类搜索的效果比 B-树更好:
=# 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
----------------
1584 kB
(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=0.390..0.391 rows=0 loops=1)
Recheck Cond: ((i2 = 898732) AND (i5 = 123451))
Rows Removed by Index Recheck: 19
Heap Blocks: exact=19
-> Bitmap Index Scan on bloomidx (cost=0.00..1792.00 rows=2 width=0) (actual time=0.361..0.361 rows=19 loops=1)
Index Cond: ((i2 = 898732) AND (i5 = 123451))
Planning Time: 0.128 ms
Execution Time: 0.415 ms
(8 rows)
现在,B-树搜索的主要问题在于,当搜索条件没有约束索引的前导列时,B-树的效率很低。对 B-树而言,更好的策略是在每一列上创建单独的索引。这样,规划器会选择类似下面的计划:
=# 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=24.34..32.03 rows=2 width=24) (actual time=0.031..0.032 rows=0 loops=1)
Recheck Cond: ((i5 = 123451) AND (i2 = 898732))
-> BitmapAnd (cost=24.34..24.34 rows=2 width=0) (actual time=0.028..0.029 rows=0 loops=1)
-> Bitmap Index Scan on btreeidx5 (cost=0.00..12.04 rows=500 width=0) (actual time=0.028..0.028 rows=0 loops=1)
Index Cond: (i5 = 123451)
-> Bitmap Index Scan on btreeidx2 (cost=0.00..12.04 rows=500 width=0) (never executed)
Index Cond: (i2 = 898732)
Planning Time: 0.474 ms
Execution Time: 0.064 ms
(9 rows)
虽然这个查询比使用任意一个单独索引时都快得多,但代价是索引更大。每个单列 B-树索引占用 2 MB,因此总共需要 12 MB,是 bloom 索引所用空间的八倍。
用于布隆索引的操作符类只需要一个针对被索引数据类型的哈希函数,以及一个用于搜索的等值操作符。下面这个示例展示了 text 数据类型的操作符类定义:
CREATE OPERATOR CLASS text_ops
DEFAULT FOR TYPE text USING bloom AS
OPERATOR 1 =(text, text),
FUNCTION 1 hashtext(text);
模块中只包含了 int4 和 text 的操作符类。
搜索只支持=操作符。不过将来有可能增加对带有并集和交集操作的数组的支持。
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,俄罗斯莫斯科