暂无图片
暂无图片
3
暂无图片
暂无图片
暂无图片

PostgreSQL Bitmap Scan

原创 Maleah 2023-02-13
2463

1、背景

PostgreSQL中扫描的方式有:

  • Sequence Scan:顺序扫描(全表扫描)。按顺序扫描所有页面
  • Index Scan:根据索引列创建的索引进行扫描,速度快
  • Index only Scan:SELECT 表的目标列都在索引键中,为了减少 I/O,仅索引扫描会直接使用索引中的键值

索引扫描在读取表的非索引键的数据时,按照以下顺序:从索引页面获取TID->从heap中取数据->从索引页面获取TID->从heap中取数据 …,索引列是有序的,但不能保证是按照存储的块位置排序,这就有可能导致随机页面访问,从而造成随机I/O。

PostgreSQL设计了Bitmap index Scan(位图索引扫描),大致思路是:首先根据索引查找,将取出所有符合条件的block id在内存中进行排序,一次性的顺序的读取page。这样既使用了索引查找快捷的特点,又避免了随机I/O。

2、原理

在观察执行计划的时候。如果使用的是位图索引扫描,我们通常会发现Bitmap Index Scan嵌套在内层,Bitmap Heap Scan在外层,如下:image-20230213153647819

在PostgreSQL中,位图扫描分为两个阶段:

  • Bitmap Index Scan 第一阶段:执行 index scan并创建位图。根据where条件的索引列,在索引中找出符合条件的行所在的page,并在内存中进行排序并创建bitmap。当找到与搜索条件匹配的索引条目时,将该位设置为 1(true),否则设置为0(false)
  • Bitmap Heap Scan 第二阶段:按照第一阶段创建的位图,从左到右顺序的读取标记为1的heap page,0的跳过

写在前面:

查了很多资料都说位图扫描读取的是block。看到一个视频,提到bitmap scan有两种level:row和block。下面是按照此视频思路进行分析:

(视频指路👉:https://www.youtube.com/watch?v=UXKYAZOWDgk&list=PL1XF9qjV8kH0ghGRGo3_f-FWqWvAbv1dh&index=39)

1)Row - Level

  • 前提:work_mem足够大
  • 步骤:根据匹配索引的row id构建位图,在顺序读取位图时,获取到匹配的行
    Row-Level

2)Page - Level

  • 前提:work_mem不足以放下所有行
  • 思路:取得符合条件的所有行号, 获得对应的block id,将block id在内存中排序并构建位图。根据位图顺序从heap 表搜索数据
    page-level

⚠️注意:由于构建的是block的位图,在扫描heap page时,需要进行recheck操作,从堆表页面中筛选出符合条件的row

3、案例

构造测试数据

maleah_db=# create table bms_test(a int,b int,c text) ;
CREATE TABLE
maleah_db=# insert into bms_test select i,ceil(random()*50),'test_'||i from generate_series(1,1000000) i; 
INSERT 0 1000000
maleah_db=# create index idx_bms_a on bms_test(a) ;
CREATE INDEX
maleah_db=# create index idx_bms_b on bms_test(b) ;
CREATE INDEX
maleah_db=# create index idx_bms_c on bms_test(c) ;
analyzeCREATE INDEX
maleah_db=# analyze ;
ANALYZE

1)Row - Level

设置 work_mem = 64MB

  • Bitmap Index Scan阶段根据b = 44进行索引扫描,并构建位图

  • Bitmap Heap Scan阶段根据构建的位图进行顺序读取,有6125个block

    如果是Row -Level的位图扫描,Recheck阶段可以省去

maleah_db=# set work_mem = '64MB';
SET
maleah_db=# explain (analyze) select * from bms_test where b = 44 ;
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on bms_test  (cost=222.13..6840.04 rows=19833 width=19) (actual time=1.592..9.852 rows=20039 loops=1)
   Recheck Cond: (b = 44)
   Heap Blocks: exact=6125
   ->  Bitmap Index Scan on idx_bms_b  (cost=0.00..217.17 rows=19833 width=0) (actual time=0.898..0.898 rows=20039 loops=1)
         Index Cond: (b = 44)
 Planning Time: 0.091 ms
 Execution Time: 10.485 ms
(7 rows)

2)Page - Level

将work_mem设置值较小

  • Bitmap Index Scan阶段根据b = 44进行索引扫描并构建位图(这里work_mem不足以放下构建的位图)
  • Bitmap Heap Scan阶段根据构建的位图进行顺序读取。

有 972 个block是精准的记录匹配的元组,而由于work_mem太小,有5153个block标记成"lossy",只记录哪些页面包含匹配的元组,而不是单独记住每个元组。在执行recheck阶段后,必须检查页面上的每个元组并重新检查扫描条件以
查看要返回哪些元组,可以看到Rows Removed by Index Recheck:792131

maleah_db=# set work_mem = '64KB';
SET
maleah_db=# explain (analyze) select * from bms_test where b = 44 ;
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on bms_test  (cost=222.13..18107.34 rows=19833 width=19) (actual time=0.925..50.501 rows=20039 loops=1)
   Recheck Cond: (b = 44)
   Rows Removed by Index Recheck: 792131
   Heap Blocks: exact=972 lossy=5153
   ->  Bitmap Index Scan on idx_bms_b  (cost=0.00..217.17 rows=19833 width=0) (actual time=0.808..0.809 rows=20039 loops=1)
         Index Cond: (b = 44)
 Planning Time: 0.087 ms
 Execution Time: 51.193 ms
(8 rows)

4、小结

  • bitmap scan目的是通过建立位图的方式,避免index scan带来的随机I/O,从而减少查询过程中的I/O消耗
  • Bitmap index Scan是在内存中完成,借助B-Tree索引实现,不占用空间,随用随构建,用完即删。
  • Bitmap Index Scan阶段是要从index中得到满足条件的tuple的heap block id。并给block id排序,构建位图;然后进入Bitmap Heap Scan阶段按照构建的位图顺序扫描heap page。
  • 位图会记录row id(row - level),但如果bitmap太大,会将位图转换成“lossy”样式(page - level):只记录block id,所以需要recheck,读取heap page时需重新检查扫描条件找出匹配的元组
  • 在数据量比较大的情况下,走bitmap scan虽然可以减少I/O代价,但不能忽视recheck阶段所带来的损耗

参考

https://dba.stackexchange.com/questions/119386/understanding-bitmap-heap-scan-and-bitmap-index-scan

https://www.postgresql.org/message-id/12553.1135634231@sss.pgh.pa.us

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论