在PostgreSQL中,对于单列上的btree索引查询,我们看到的都是普通的索引扫描Index Scan,比如下面这样:
bill=# explain select * from t1 where c1=10;
QUERY PLAN
--------------------------------------------------------------------
Index Scan using idx_t1 on t1 (cost=0.15..10.73 rows=10 width=12)
Index Cond: (c1 = 10)
(2 rows)
对于多个单列索引是用在组合查询SQL中的场合下,我们可以看到使用的是bitmap scan,例如:
bill=# create index idx_t11 on t1 using btree(c1);
CREATE INDEX
bill=# create index idx_t12 on t1 using btree(c2);
CREATE INDEX
bill=# create index idx_t13 on t1 using btree(c3);
CREATE INDEX
bill=# explain select * from t1 where c1 =10 and c2 =20 and c3 = 30;
QUERY PLAN
-----------------------------------------------------------------------------
Bitmap Heap Scan on t1 (cost=3.31..4.62 rows=1 width=12)
Recheck Cond: ((c3 = 30) AND (c2 = 20))
Filter: (c1 = 10)
-> BitmapAnd (cost=3.31..3.31 rows=1 width=0)
-> Bitmap Index Scan on idx_t13 (cost=0.00..1.53 rows=10 width=0)
Index Cond: (c3 = 30)
-> Bitmap Index Scan on idx_t12 (cost=0.00..1.53 rows=10 width=0)
Index Cond: (c2 = 20)
(8 rows)
那么这两者有什么区别呢?PostgreSQL又是如何处理多个组合条件的BITMAP SCAN的呢?
首先我们来说一下Index Scan,这个是最简单的btree索引扫描,其原理是通过索引key去寻找元组的ctid,然后回表获取对应的数据(index only scan除外).
而BITMAP SCAN是对于每个查询条件,在对应索引中找到符合条件的堆表PAGE,每个索引构造一个bitmap串。在这个bitmap串中,每一个BIT位对应一个HEAP PAGE,代表这个HEAP PAGE中有符合该条件的行(只要任意一行符合条件则该PAGE的BIT位就会被标位1)。根据条件的多少,组成了多个bitmap。
如下图所示:
+---------------------------------------------+
|100000000001000000010000000000000111100000000| bitmap 1
|000001000001000100010000000001000010000000010| bitmap 2
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|000000000001000000010000000000000010000000000| Combined bitmap
+-----------+-------+--------------+----------+
| | |
v v v
Used to scan the heap only for matching pages:
+---------------------------------------------+
|___________X_______X______________X__________|
+---------------------------------------------+
例如我们在c1列上创建了一个索引,我们使用c1=10的查询就会创建bitmap1这样一个bitmap串,然后c2=20创建bitmap2这样的bitmap串,然后对这两个bitmap串进行and操作,得到了combined bitmap这样一个bitmap串,但此时我们注意到前面的执行计划最后还有一步Recheck Cond,这是干嘛的呢?正如我们前文所说,每一个BIT位对应一个HEAP PAGE,我们需要去每一个page上去recheck得到需要的行,这就是Recheck Cond的作用.
最后我们来看看有哪些场景会使用bitmap scan呢?
1、上文所举例的 btree索引的多个组合查询
不同列的 and/or 查询
相同列的 or 查询
2、 brin 索引
由于brin索引本身存储的就是一些连续块的元信息,所以本身就无法实现精确查询,所以通过brin查询时,首先也是构建heap page的bitmap串,(符合条件的为1,不符合条件的为0),然后根据这个bitmap串搜索tuple.
并在bitmap heap scan阶段 recheck 条件。
bill=# create table t_brin (id int, info text, crt_time timestamp);
CREATE TABLE
bill=# insert into t_brin select generate_series(1,1000000), md5(random()::text), clock_timestamp();
INSERT 0 1000000
bill=# create index idx_t_brin_1 on t_brin using brin (id) with (pages_per_range=1);
CREATE INDEX
bill=# explain (analyze,verbose,timing,costs,buffers) select * from t_brin where id between 100 and 200;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on public.t_brin (cost=38.52..175.79 rows=88 width=45) (actual time=2.100..2.116 rows=101 loops=1)
Output: id, info, crt_time
Recheck Cond: ((t_brin.id >= 100) AND (t_brin.id <= 200))
Rows Removed by Index Recheck: 113
Heap Blocks: lossy=2
Buffers: shared hit=45
-> Bitmap Index Scan on idx_t_brin_1 (cost=0.00..38.50 rows=107 width=0) (actual time=2.082..2.082 rows=20 loops=1)
Index Cond: ((t_brin.id >= 100) AND (t_brin.id <= 200))
Buffers: shared hit=43
Planning Time: 0.224 ms
Execution Time: 2.145 ms
(11 rows)
3、gin 索引
gin 索引存储的是KEY,以及ctid (heap行号)组成的posting list或posting tree,它理论上是可以支持index scan的,但是PostgreSQL目前仅对GIN实施了bitmap scan。
所以在使用gin索引时,首先也是构造heap page的bitmap串,(符合条件的为1,不符合条件的为0),然后根据这个bitmap串搜索tuple.
并在bitmap heap scan阶段 recheck 条件。
这也是目前gin值得改进的地方。
bill=# create table t_gin2 (id int, c1 int);
CREATE TABLE
bill=# insert into t_gin2 select generate_series(1,100000), random()*10 ;
INSERT 0 100000
bill=# create index idx_t_gin2_1 on t_gin2 using gin (c1);
CREATE INDEX
bill=# explain (analyze,verbose,timing,costs,buffers) select * from t_gin2 where c1=1;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on public.t_gin2 (cost=9.07..349.73 rows=500 width=8) (actual time=1.084..3.279 rows=10038 loops=1)
Output: id, c1
Recheck Cond: (t_gin2.c1 = 1)
Heap Blocks: exact=443
Buffers: shared hit=448
-> Bitmap Index Scan on idx_t_gin2_1 (cost=0.00..8.95 rows=500 width=0) (actual time=1.032..1.032 rows=10038 loops=1)
Index Cond: (t_gin2.c1 = 1)
Buffers: shared hit=5
Planning Time: 0.137 ms
Execution Time: 3.775 ms
(10 rows)