PostgreSQL中的bitmap scan解析

在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)

你可能感兴趣的:(PostgreSQL)