之前了解过postgresql的Bitmap scan,只是粗略地了解到是通过标记数据页面来实现数据检索的,执行计划中的的Bitmap scan一些细节并不十分清楚。这里借助一个执行计划来分析bitmap scan以及index only scan,以及两者的一些区别。
这里有关于Bitmap scan的一些实现过程,https://dba.stackexchange.com/questions/119386/understanding-bitmap-heap-scan-and-bitmap-index-scan0. 构建测试环境

如下测试脚本,构建一个简单的测试表

create table my_test_table01
(
c1 serial not null primary key,
c2 varchar(100),
c3 timestamp
)
--c3字段上建索引
create index ix_c3 on my_test_table01(c3); truncate table my_test_table01; --写入300W行测试数据,c3列生成随机时间
insert into my_test_table01 (c2,c3)
select uuid_generate_v1(),NOW() - (random() * (NOW()+'1000 days' - NOW())) from generate_series(1,3000000);

1. Bitmap Scan的剖析

用最最容易理解的场景来测试Bitmap Index Scan,执行如下sql,来分析bitma scan这个执行计划的含义。

explain (analyze, buffers,verbose,timing)
select count(1) from my_test_table01 a
where a.c3 >'20220328' or a.c1 < 100;

对以上的执行计划,有几个问题先弄清楚:
1,Bitmap Index Scan做了什么?
2,Bitmap Heap Scan做了什么?
3,Recheck Cond的目的是什么?

第一个问题:Bitmap Index Scan做了什么?
一个查询条件的Bitmap是一个bit数组,bit数组中的每一位映射到表中的一个数据页IdOne bit per heap page, in the same order as the heap)。
Bitmap Index Scan对于所有的查询条件,从扫描索引的所有页面,如果数据页面中有复合条件的数据,那么就将bit为标记为1,否则标记为0。
其他的查询条件依次创建一个一样的bit数组,同样扫描索引的所有页面,将符合条件的page的bit位标记为1。
最后多个条件生成的多个bit数组进行与(或)操作(取决于where多个条件是and组合或者or组合,上面截图中的BitmapOr),合并成一个最终的bit数组。
此时最终的bit数组标记的复合条件的数据页,而不是最终的数据行,所以最终还要去数据页中进行筛选。
bitmap index scan 内部优化机制:https://www.postgresql.org/message-id/12553.1135634231@sss.pgh.pa.us

第二个问题:Bitmap Heap Scan做了什么
而BitMap Index Scan一次性将满足条件的索引项全部取出,并在内存中进行排序, 然后根据取出的索引项访问表数据,也就是执行计划中的Bitmap Heap Scan。

第三个问题:Recheck Cond的目的是什么
BitMap Heap Scan指示找到符合条件的数据页面,而不是具体的记录,此时找到数据页后再用where条件进行筛选,也就是执行计划中的Recheck Cond。
https://stackoverflow.com/questions/50959814/what-does-recheck-cond-in-explain-result-mean
If the bitmap gets too large we convert it to "lossy" style, in which we only remember which pages contain matching tuples instead of remembering each tuple individually. When that happens, the table-visiting phase has to examine each tuple on the page and recheck the scan condition to see which tuples to return.

bitmap scan示例图

图片来源:https://www.cybertec-postgresql.com/en/postgresql-indexing-index-scan-vs-bitmap-scan-vs-sequential-scan-basics/bitmap index scan不仅仅发生在where条件中有多个筛选条件的场景(比如where c1 = m and c2 =n),其实对于一个条件的范围查询,也同样适用bitmap index scan,见下例。

2. 为什么执行计划走Bitmap Index Scan,而不是Index only Scan?

对于如下这个查询,表中有300W测试数据符合条件的数据比例很少,很明显,ix_c3上的索引扫描才是更优化的执行计划,为什么在默认情况下是Bitmap Index Scan?

select count(1) from my_test_table01 a
where a.c3 >'20220328' ;

从如下截图可以看到,vacuum是打开的,在造完测试数据后,默认情况下上述sql查询走了bitmap Index scan,因为c3上有索引,预期是走ix_c3上的索引。
原本以为vacuum是异步的,或者说有滞后性,但是这个case在测试数据构造完之后几个小时甚至几天,该查询都依旧走bitmap Index scan的方式。
当关闭enable_bitmapscan和enable_seqscan,强制优化器走ix_c3上的index only scan,代价明显更大,这就有点说不通了,原因下文会具体分析。

本人对该现象一开始也是百思不得其解,难道是bitmap scan有什么魔法?

看到这里有一个提到这个问题:https://www.datadoghq.com/blog/postgresql-vacuum-monitoring/,里面相关的内容的是这么说的:
1. Large insert-only tables.  Large insert-only tables are not automatically vacuumed (except for transaction-ID wraparound), because autovacuum is triggered by updates and deletes.  This is generally a good thing, because it saves a great deal of not-very-useful work.  However, it's problematic for index-only scans, because it also means the visibility map bits won't get set.  I don't have a very clear idea what to do about this, but it's likely to need thought and work.  For a first version of this feature, we'll likely have to rely on users to do a manual VACUUM in this case.

既然这种场景无法主从出发vacuum,那么这里就手动vacuum测试表,然后打开bitmap scan选项,继续观察此时的默认情况下,该查询是不是可以走index only scan,这一次终于是预期的ix_c3上的index only scan了。

同时还有一个疑问:对表执行vacuum前后,index only scan的shared hit差别这么大?
上述得知在large-insert的情况下,不会触发表上的vacuum,此时如果强制使用index only scan,因为索引上的没有数据行的可见性信息(Index Only Scan operation must visit the heap to check if the row is visible.)所以在vacuum之前,强制使用index only scan的过程中,对于任何一行数据都要回表进行可见性判断,因此会产生大量的shared hit。一旦vacuum之后,由于索引上更新了数据行的可见性,不需要回表判断,因此shared hit会大幅度地降低。

3. 主动触发vacuum.
 Large insert-only tables are not automatically vacuumed,也就是大批量的插入无法主动发出vacuum,vacuum由update和delete产生,那么尝试对表执行一些update或者delete操作,会不会主动触发vacuum?
基于第一步的脚本,重新初始化测试表,在插入300W行数据后,删除其中一部分数据,目前是让delete操作触发vacuum,然后再通过执行计划,观察是否会想手动vacuum一样,走index only scan。
经过三次删除,完美触发vacuum,执行计划有一开始bitmap scan更新为index only scan。

4. bitmp index scan VS index-only scan
参考这里https://www.cybertec-postgresql.com/en/killed-index-tuples/ 对 bitmap  index scan 和 index-only scan的解释

PostgreSQL 8.1 introduced the “bitmp index scan”. This scan method first creates a list of heap blocks to visit and then scans them sequentially. 
This not only reduces the random I/O, but also avoids that the same block is visited several times during an index scan. PostgreSQL 9.2 introduced the “index-only scan”, which avoids fetching the heap tuple.
This requires that all the required columns are in the index and the “visibility map” shows that all tuples in the table block are visible to everybody.

bitmp index scan不仅可以避免随机的IO操作,而且可以避免同一个页面(在一个查询执行过程中)被重复读取(一个页面中可能存在多条满足查询条件的元组,其他方式可能会多次读取同一个页面)。
index-only scan避免了从堆中读取数据,但是他要求所有请求的字段都在索引中,并且“visibility map” 中显示表块中的所有元组对所有事物都是可见的,但是索引中并不包含元组的可见性。

本文通过一个看似不起眼的问题sql执行计划的分析,尝试分析bitmap scan 和index only scan的差异以及选择二者的原因,同时会涉index索引元组的可见性及vacuum没有触发的一些特殊场景。一个问题往往不是一个点,是一系列问题的合集,此事要躬行。

参考链接:

https://stackoverflow.com/questions/55651068/why-is-bitmap-scan-faster-than-index-scan-when-fetching-a-moderately-large-perce
https://ask.use-the-index-luke.com/questions/148/why-is-this-postgres-query-doing-a-bitmap-heap-scan-after-the-index-scan
http://rhaas.blogspot.com/2010/11/index-only-scans.html

PostgreSQL执行计划:Bitmap scan VS index only scan的更多相关文章

  1. 深入理解Oracle索引(1):INDEX SKIP SCAN 和 INDEX RANGE SCAN

    ㈠ Index SKIP SCAN                当表有一个复合索引,而在查询中有除了索引中第一列的其他列作为条件,并且优化器模式为CBO,这时候查询计划就有可能使用到SS       ...

  2. PostgreSQL 执行计划

    简介 PostgreSQL是“世界上最先进的开源关系型数据库”.因为出现较晚,所以客户人群基数较MySQL少,但是发展势头很猛,最大优势是完全开源. MySQL是“世界上最流行的开源关系型数据库”.当 ...

  3. PostgreSQL执行计划的解析

    一个顺序磁盘页面操作的cost值由系统参数seq_page_cost (floating point)参数指定的,由于这个参数默认为1.0,所以我们可以认为一次顺序磁盘页面操作的cost值为1.下面o ...

  4. postgreSQL执行计划

    " class="wiz-editor-body wiz-readonly" contenteditable="false"> explain命 ...

  5. INDEX FAST FULL SCAN和INDEX FULL SCAN

    INDEX FULL SCAN 索引全扫描.单块读 .它扫描的结果是有序的,因为索引是有序的.它通常发生在 下面几种情况(注意:即使SQL满足以下情况 不一定会走索引全扫描) 1. SQL语句有ord ...

  6. index unique scan 与index range scan等的区别

    存取Oracle当中扫描数据的方法(一) Oracle 是一个面向Internet计算环境的数据库.它是在数据库领域一直处于领先地位的甲骨文公司的产品.可以说Oracle关系数据库系统是目前世界上流行 ...

  7. oralce索引中INDEX SKIP SCAN 和 INDEX RANGE SCAN区别

    INDEX SKIP SCAN 当表中建立有复合索引的时候,查询时,除复合索引第一列外,别的列作为条件时,且优化器模式为CBO,这个时候查询可能会用到INDEX SKIP SCAN skip scan ...

  8. [20180316]为什么不使用INDEX FULL SCAN (MIN/MAX).txt

    [20180316]为什么不使用INDEX FULL SCAN (MIN/MAX).txt --//链接:http://www.itpub.net/thread-2100456-1-1.html.自己 ...

  9. PostgreSQL 与 Oracle 访问分区表执行计划差异

    熟悉Oracle 的DBA都知道,Oracle 访问分区表时,对于没有提供分区条件的,也就是在无法使用分区剪枝情况下,优化器会根据全局的统计信息制定执行计划,该执行计划针对所有分区适用.在分析利弊之前 ...

随机推荐

  1. 什么是phpize及其用法

    应用场景在使用php的过程中,我们常常需要去添加一些PHP扩展库.但是重新对php进行编译是比较蛮烦的,所以这时候我们可以使用phpize对php进行添加扩展.并且phpize编译的扩展库可以随时启用 ...

  2. JavaScript 数组方法filter和reduce

    前言 在ES6新增的数组方法中,包含了多个遍历方法,其中包含了用于筛选的filter和reduce filter 主要用于筛选数组的filter方法,在使用中,不会改变原数组,同时会将符合筛选条件的元 ...

  3. java方法学习

    java方法学习 方法概念 什么是方法 方法就是完成某些事情的过程,如:实现两个数相加,用方法add(数值1,数值2). 1.System.out.print(),System是系统的一个类,out是 ...

  4. largebin attack

    largebin attack 由这个名字就可以看出是对 largebin 进行的操作,需要的条件是存在 UAF 或者可以构造出 UAF.实现的功能是: 1.任意地址写入一个大数字 2.实现任意地址分 ...

  5. 常见的Socket网络异常场景分析

    原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 在目前微服务的背景下,网络异常越来越常见了,而有一些网络异常非常模糊,理解什么情况下会导致什么异常,还是有一定难度 ...

  6. stash —— 一个极度实用的Git操作

    今天要介绍的 Git 操作就是 stash,毫不夸张地说,每个用 Git 的开发人员都一定要会懂怎么使用. 在介绍之前,不知道你有没有和我一样的经历:某一天,我正在一个 feature 分支上高高兴兴 ...

  7. Java基础-反转数组

    /** java基础,如何将一个数组反转,思考方法采用使用临时变量的情况下,将第一个元素与第二个元素进行反转,需要定义两个索引,一个记录数组的第一个元素与最后一个元素,将其两两交换* */public ...

  8. Android 12(S) 图形显示系统 - 解读Gralloc架构及GraphicBuffer创建/传递/释放(十四)

    必读: Android 12(S) 图形显示系统 - 开篇 一.前言 在前面的文章中,已经出现过 GraphicBuffer 的身影,GraphicBuffer 是Android图形显示系统中的一个重 ...

  9. 半吊子菜鸟学Web开发 -- PHP学习2-正则,cookie和session

    1正则表达式 1.1基本的匹配字符串 $p = '/apple/'; $str = "apple banna"; if (preg_match($p, $str)) { echo ...

  10. Mysql之B+树索引实战

    索引代价 空间上的代价 一个索引都对应一棵B+树,树中每一个节点都是一个数据页,一个页默认会占用16KB的存储空间,所以一个索引也是会占用磁盘空间的. 时间上的代价 索引是对数据的排序,那么当对表中的 ...