2015-04-06 82 views
6

我有一个16列的表,其中有一个主键和一列存储值。 我想选择一定范围内的所有值。 值列(easyid)已编入索引。为什么这个查询不使用postgresql中的仅索引扫描

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) 
+1

向我们展示了'explain(analyze,verbose)'的输出结果 – 2015-04-06 08:36:07

+0

您的表中没有足够的数据可能让规划人员无法理解索引。要查看索引是否会被使用,请将'set enable_seqscan = off;'in输入到控制台并再次尝试。这将使PostgreSql尽可能地避免顺序扫描。 – jgm 2015-04-06 08:39:58

+0

@a_horse_with_no_name解释加入 – worldterminator 2015-04-06 09:13:04

回答

9

自动清理不运行

PostgreSQL只索引扫描需要一些有关哪些行是“看得见”,以目前的交易 - 即不删除,不老的版本更新的行,不提交插入或更新的新版本。

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

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

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

重新打开autovaccum。然后手动VACUUM表使其立即得到更新。

顺便说一句,除了可见性地图信息之外,汽车VACUUM还可以编写提示位信息,这可以使得最近插入/更新数据的速度更快。

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

这也是绝对至关重要防止称为事务-ID环绕的一个问题,这是可以导致整个数据库进入紧急关闭,直到一个耗时的整个表VACUUM紧急情况被执行。

请勿关闭自动清除功能

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

  • 目前random_page_cost设置使得它认为随机I/O会比它慢真的是,所以它尽量避免它;

  • 表统计数据,特别是极限值已过时。所以它没有意识到,只有索引扫描才能快速发现所寻找的价值。

  • 可见性映射已过时,所以它认为只有索引的扫描会发现太多值,这将需要堆取回来检查,使其比其他方法更慢,特别是如果它认为可能找到值的比例高。

大多数的这些问题是由固定自动清理留下单独。事实上,在频繁附加的表格上,您应该设置autovacuum运行,比默认运行更频繁,因此它会更多地更新限制统计信息。 (这样做有助于解决PostgreSQL的规划问题,其中最常用的查询数据是最近插入的表,最新插入的ID或时间戳记表示最希望的值绝不会在表格直方图和限制统计数据中)。

将自动清理转回 - 然后打开它。

+0

我想我犯了一个错误。我的autovacuum已开启。我认为这是一个单独的过程,我没有用'ps -aux |找到它grep真空'。我以这种方式解决问题:'CLUSTER tb1 USING i_easyid'。现在范围选择要快得多,并且大部分时间只使用“索引扫描”。 – worldterminator 2015-04-07 01:33:12

2

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

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

这可以通过更改random_page_cost的值进行部分测试,如果您使用速度非常快的磁盘或SSD,则值得研究。

相关问题