PostgreSQL索引不用于范围查询

时间:2013-01-18 21:13:28

标签: postgresql database-design indexing between

我正在使用PostgreSQL(9.2.0)并拥有一个IP范围表。这是SQL:

CREATE TABLE ips
(
  id serial NOT NULL,
  begin_ip_num bigint,
  end_ip_num bigint,
  country_name character varying(255),
  CONSTRAINT ips_pkey PRIMARY KEY (id )
)

我在begin_ip_numend_ip_num添加了索引:

CREATE INDEX index_ips_on_begin_ip_num
  ON ips
  USING btree
  (begin_ip_num );

CREATE INDEX index_ips_on_end_ip_num
  ON ips
  USING btree
  (end_ip_num );

使用的查询是:

SELECT "ips".* FROM "ips" WHERE (3065106743 BETWEEN begin_ip_num AND end_ip_num);

问题是我的BETWEEN查询仅使用begin_ip_num上的索引。使用索引后,它使用end_ip_num过滤结果。这是EXPLAIN ANALYZE结果:

Index Scan using index_ips_on_begin_ip_num on ips  (cost=0.00..2173.83 rows=27136 width=76) (actual time=16.349..16.350 rows=1 loops=1)
Index Cond: (3065106743::bigint >= begin_ip_num)
Filter: (3065106743::bigint <= end_ip_num)
Rows Removed by Filter: 47596
Total runtime: 16.425 ms

我已经尝试了各种索引组合,包括在begin_ip_numend_ip_num上添加综合索引。

4 个答案:

答案 0 :(得分:24)

尝试multicolumn index,但第二列的顺序相反:

CREATE INDEX index_ips_begin_end_ip_num ON ips (begin_ip_num, end_ip_num DESC);

排序与单列索引无关,因为它可以几乎同样快速地向后扫描。但它对多列索引很重要。

根据我提出的索引,Postgres可以扫描第一列并找到地址,其中索引的其余部分满足第一个条件。然后,对于第一列的每个值,它可以返回满足第二个条件的所有行,直到第一个列失败。然后跳转到第一列的下一个值,等等 这 仍然效率不高 ,Postgres可能更快,只需扫描第一个索引列并过滤第二个索引列。很大程度上取决于您的数据分布。

这里真正有用的是GiST index for a int8range column,自PostgreSQL 9.2以来可用。

除此之外,您可以使用部分索引的相当复杂的制度查看此closely related answer on dba.SE。高级的东西,但它提供了很好的性能。

无论哪种方式,CLUSTER使用上面的多列索引可以帮助提高性能:

CLUSTER ips USING index_ips_begin_end_ip_num

这样,满足您第一个条件的候选人被打包到相同或相邻的数据页面上。如果第一列的每个值有很多行,可以帮助提高性能。否则它几乎没有效果。

此外,autovacuum正在运行,还是您在桌面上运行ANALYZE?您需要Postgres的当前统计信息来选择适当的查询计划。

答案 1 :(得分:4)

我参加这个派对有点晚了,但这对我来说真的很好。

考虑安装ip4r extension。它基本上允许您定义可以保存IP范围的列。扩展名称意味着它仅适用于IPv4,但目前它也支持IPv6。

在您使用该列中的范围填充表后,只需要创建GIST索引:

CREATE INDEX ip_zip_ip4_range ON ip_zip USING gist (ip4_range);

我的数据库中有近1000万个范围,但查询只需要几分之一毫秒:

region=> select count(*) from ip_zip ;

  count  
---------
 9566133

region=> explain analyze select * from ip_zip where '8.8.8.8'::ip4 <<= ip4_range;
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on ip_zip  (cost=234.55..25681.29 rows=9566 width=22) (actual time=0.085..0.086 rows=1 loops=1)
   Recheck Cond: ('8.8.8.8'::ip4r <<= ip4_range)
   Heap Blocks: exact=1
   ->  Bitmap Index Scan on ip_zip_ip4_range  (cost=0.00..232.16 rows=9566 width=0) (actual time=0.055..0.055 rows=1 loops=1)
         Index Cond: ('8.8.8.8'::ip4r <<= ip4_range)
 Planning time: 0.106 ms
 Execution time: 0.118 ms
(7 rows)

region=> explain analyze select * from ip_zip where '254.50.22.54'::ip4 <<= ip4_range;
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on ip_zip  (cost=234.55..25681.29 rows=9566 width=22) (actual time=0.059..0.059 rows=1 loops=1)
   Recheck Cond: ('254.50.22.54'::ip4r <<= ip4_range)
   Heap Blocks: exact=1
   ->  Bitmap Index Scan on ip_zip_ip4_range  (cost=0.00..232.16 rows=9566 width=0) (actual time=0.048..0.048 rows=1 loops=1)
         Index Cond: ('254.50.22.54'::ip4r <<= ip4_range)
 Planning time: 0.102 ms
 Execution time: 0.145 ms
(7 rows)

答案 2 :(得分:3)

我在maxmind.com的免费geiop表中几乎完全相同的数据集上遇到了同样的问题。我使用Erwin关于范围类型和GiST索引的提示解决了这个问题。 GiST指数很关键。没有它,我最多只能查询每秒3行。有了它,我在10秒内查询了近50万行!由于Erwin没有发布关于如何做到这一点的详细说明,我想我会在这里添加它们......

首先,您必须添加具有范围类型的新列,请注意bigint类型需要int8range。接下来适当地设置其值,请注意&#39; []&#39;参数表示在下限和上限(rtfm)中使范围包含。最后添加索引,注意GiST索引是所有性能优势的来源。

alter table ips add column iprange int8range;
update ips set iprange=int8range(begin_ip_num, end_ip_num, '[]');
create index index_ips_on_iprange on ips using gist (iprange);

奠定了基础后,您现在可以使用&#39;&lt; @&#39;包含操作符以针对表搜索特定地址。见http://www.postgresql.org/docs/9.2/static/functions-range.html

SELECT "ips".* FROM "ips" WHERE (3065106743::bigint <@ iprange);

答案 3 :(得分:0)

我相信您的查询看起来像WHERE [constant] BETWEEN begin_ip_num AND end_ipnum

据我所知,Postgres没有“AND-EQUAL”访问计划,因此您需要按照 Erwin Brandstetter 的建议在2列上添加复合索引。