postgresql中的各种scan的比较
最近在看postgresql的查询计划,在查询中对表的扫描计划大概有如下几种:
Seq Scan
Index Scan
Bitmap Heap Scan
Index Only Scan
这里就把自己的理解写下来,欢迎大家批评指正:
1)对于Seq Scan很好理解,就是按照表的记录的排列顺序从头到尾依次检索扫描,每次扫描要取到所有的记录。这也是最简单最基础的扫表方式,扫描的代价比较大;
2)对于Index Scan,我们也很熟悉,对于给定的查询,我们先扫描一遍索引,从索引中找到符合要求的记录的位置(指针),再定位到表中具体的Page去取。等于是两次I/O,先走索引,再走取表记录,不同于全表扫描的是只取所需数据对应的Page,I/O量较小;
3)对于Bitmap Heap Scan不是很常见,我翻阅了postgresql的手册,找到以下几句话:
A plain indexscan fetches one tuple-pointer at a time from the index, and immediately visits that tuple in the table. A bitmap scan fetches all the tuple-pointers from the index in one go, sorts them using an in-memory "bitmap" data structure, and then visits the table tuples in physical tuple-location order. The bitmap scan improves locality of reference to the table at the cost of more bookkeeping overhead to manage the "bitmap" data structure --- and at the cost that the data is no longer retrieved in index order, which doesn't matter for your query but would matter if you said ORDER BY.
上面的意思是说,普通的索引扫描( index scan)一次只读一条索引项,那么一个 PAGE 面有可能被多次访问;而 bitmap scan 一次性将满足条件的索引项全部取
出,并在内存中进行排序, 然后根据取出的索引项访问表数据。当 PostgreSQL 需要合并索引访问的结果子集时 会用到这种方式 ,通常情况是在用到 "or",“and”时会出现"Bitmap heap scan"。
4)所谓index only scan ,就是因为 建立 index时,所包含的字段集合,囊括了我们查询语句中的字段,这样,提取出相应的index ,就不必再次提取数据块了。
举个例子:对于表:
create table test(id int, name text, age int);
insert into test select generate_series(1,100000),'test'::text,generate_series(1,100000);
我们对id和age建立复合索引:
create index test_id_age on test(id ,age);
然后,执行查询:
explain select id, age from test where id < 20 and age >0;
查询结果为:
postgres=# explain select id ,age from test where id < 20 and age >0;
QUERY PLAN
-------------------------------------------------------------------------------
Index Only Scan using test_id_age on test (cost=0.29..41.94 rows=20 width=8)
Index Cond: ((id < 20) AND (age > 0))
(2 rows)
这个查询里查询的id和age就在索引test_id_age上,在我们取出索引的时候,我们已经获取了(id,age)值的序列,因此就不必再去表中获取记录了,在Index上我们就获得了我们需要的数据,因此称为Index Only Scan。
那么这几种表的扫描的应用场景呢?是不是走索引就一定比全表扫描好呢?也不尽然。
我们知道全表扫描是直接扫描全表,而Index Scan是走一次索引再定位表所在的Page,那么我们可以推断:
当获取的数据分布很大(比如70%以上)时,用index scan 已经没有意义了,因为数据太多了,走索引再走表的代价已经超过了单纯走表的代价了。就不如用全表扫描了。
而数据分布较小(比如 1.7%),则索引的优势就体现出来了。可能bitmap index scan的性能就更好(相比于index scan,因为它减少了index的重复扫描)。
当数据更少的时候,用index scan可能就更好(索引重复的可能性较小且回避了在内存中排序的代价)。
需要引起注意的是, bitmap index scan也可以用在where 条件单一的时候。
而对于Index Only Scan,由于不需要扫描表的数据块,只走索引,那么在能满足条件的情况下几乎是最快的了(当然我也没有数据验证)。
对于索引的讨论一直是热门话题,这里只是简单的提到,下次遇到再详细的写写吧。