为什么此查询不在postgresql中使用仅索引扫描

时间:2015-04-06 08:03:22

标签: sql postgresql

我有一个包含16列的表,其中有一个主键和一个用于存储值的列。 我想选择某个范围内的所有值。 值列(easyid)已被编入索引。

create table tb1 (
    id Int primary key,
    easyid Int,
    .....
)
create index i_easyid on tb1 (easyid)

其他信息:postgresql 9.4,没有自动真空。 sql就是这样的。

select "easyid" from "tb1" where "easyid" between 12183318 and 82283318

理论上postgresql应该在i_easyid上使用仅索引扫描。 仅在范围"easyid" between A and B较小时才进行索引扫描。 当范围很大,即B-A是一个相当大的数字时,postgresql在i_easyid上使用位图索引扫描,然后在tb1上进行位堆扫描。

我错误地说索引扫描与否取决于范围大小。 我尝试了不同参数的相同查询,有时它只是索引扫描,有时它不是。

表格tb1非常大,最高可达17G。 i_easyid是600MB。

这是sql的解释。我不明白为什么4000行的成本可能超过10秒。

sample_pg=# explain analyze select easyid from tb1 where "easyid" between 152183318 and 152283318;
                                                         QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on tb1  (cost=97.70..17227.71 rows=4416 width=4) (actual time=1.155..14346.311 rows=5004 loops=1)
   Recheck Cond: ((easyid >= 152183318) AND (easyid <= 152283318))
   Heap Blocks: exact=4995
   ->  Bitmap Index Scan on i_easyid  (cost=0.00..96.60 rows=4416 width=0) (actual time=0.586..0.586 rows=5004 loops=1)
         Index Cond: ((easyid >= 152183318) AND (easyid <= 152283318))
 Planning time: 0.080 ms
 Execution time: 14348.037 ms
(7 rows)

以下是仅索引扫描的示例:

sample_pg=# explain analyze verbose select easyid from tb1 where "easyid" between 32280318 and 32283318;
                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
 Index Only Scan using i_easyid on public.tb1  (cost=0.44..281.82 rows=69 width=4) (actual time=14.585..160.624 rows=33 loops=1)
   Output: easyid
   Index Cond: ((tb1.easyid >= 32280318) AND (tb1.easyid <= 32283318))
   Heap Fetches: 33
 Planning time: 0.085 ms
 Execution time: 160.654 ms
(6 rows)

2 个答案:

答案 0 :(得分:9)

  

autovacuum未运行

PostgreSQL仅索引扫描需要一些关于哪些行对当前事务“可见”的信息 - 即未删除,而不是旧版本的更新行,而不是未提交的插入或新版本的更新。

此信息保存在“可见性图”中。

可见性图由VACUUM维护,通常由autovacuum工作人员在后台维护。

如果autovacuum没有很好地跟上写入活动,或者如果autovacuum已被禁用,则可能不会使用仅索引扫描,因为PostgreSQL会看到可见性映射没有足够的表的数据。

重新打开autovaccum。然后手动VACUUM表格,立即将其更新。

除了可见性地图信息之外,自动VACUUM还可以写入提示位信息,使得最近插入/更新的数据的SELECT更快。

Autovacuum还维护对有效查询计划至关重要的表统计信息。关闭它将导致计划者使用越来越陈旧的信息。

它也是绝对重要的,用于防止称为事务ID环绕的问题,这是一种紧急情况,可能导致整个数据库进入紧急关闭状态,直到耗费时间执行整个表VACUUM

不要关闭autovacuum

至于为什么它有时使用仅索引扫描而有时不使用,有几种可能性:

  • 当前random_page_cost设置使其认为随机I / O会比实际速度慢,所以它会更加努力地避免它;

  • 表统计信息,特别是限制值已过时。因此,它没有意识到在一个仅索引的扫描中很快就会发现所寻找的值;

  • 可见性图已过时,因此它认为仅索引扫描会找到太多值,需要检查堆读取,使其比其他方法慢,特别是如果它认为值的比例可能是发现很高。

大多数问题都是由单独保留autovacuum 修复的。实际上,在频繁附加的表中,您应该将autovacuum设置为比默认值更频繁地运行 ,以便更新更新限制统计信息。 (这样做有助于解决PostgreSQL的计划程序问题,其中最常查询的数据是最近插入的,具有递增ID或时间戳,这意味着最期望的值永远不会出现在表格直方图和限制统计数据中)。

重新启用autovacuum - 然后将其打开。

答案 1 :(得分:2)

我不是百分百肯定,但我怀疑PostgreSQL认为读取表比索引更快,因为random_page_cost。索引读取的成本可能更高,因为需要在其中找到基本上随机的页面。

从表中检索的数据将需要排序,但计算可能表明(顺序表读取+排序)的总成本大于(随机索引读取)。

通过更改random_page_cost的值可以部分测试,如果您使用的是非常快的磁盘或SSD,那么值得研究。