PostgreSQL索引不用于范围查询

Zai*_*far 9 postgresql indexing database-design 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 )
)
Run Code Online (Sandbox Code Playgroud)

我已经添加了两个指数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 );
Run Code Online (Sandbox Code Playgroud)

使用的查询是:

SELECT "ips".* FROM "ips" WHERE (3065106743 BETWEEN begin_ip_num AND end_ip_num);
Run Code Online (Sandbox Code Playgroud)

问题是我的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
Run Code Online (Sandbox Code Playgroud)

我已经尝试过指数的各种组合,包括增加两个综合指数begin_ip_numend_ip_num.

Erw*_*ter 26

尝试多列索引,但第二列的顺序颠倒:

CREATE INDEX index_ips_begin_end_ip_num ON ips (begin_ip_num, end_ip_num DESC);
Run Code Online (Sandbox Code Playgroud)

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

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

这里真正有用的是一个GiST索引int8range,自PostgreSQL 9.2起可用.

除此之外,您可以在dba.SE上查看这个密切相关的答案,其中包含一个相当复杂的部分索引制度.高级的东西,但它提供了很好的性能.

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

CLUSTER ips USING index_ips_begin_end_ip_num
Run Code Online (Sandbox Code Playgroud)

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

是,autovacuum运行还是你ANALYZE在桌子上运行?您需要Postgres的当前统计信息来选择适当的查询计划.


pbn*_*son 6

我在来自 maxmind.com 的免费 geiop 表的几乎相同的数据集上遇到了完全相同的问题。我使用 Erwin 关于范围类型和 GiST 索引的提示解决了这个问题。GiST 索引是关键。没有它,我最多每秒查询 3 行。有了它,我在 10 秒内查询了近 500000 行!由于 Erwin 没有发布有关如何执行此操作的详细说明,我想我会在此处添加它们...

首先,您必须添加一个具有范围类型的新列,注意 bigint 类型需要 int8range。接下来适当地设置它的值,注意'[]'参数指示使范围包含在下限和上限(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);
Run Code Online (Sandbox Code Playgroud)

打好基础后,您现在可以使用 '<@' 包含运算符来搜索表中的特定地址。请参阅http://www.postgresql.org/docs/9.2/static/functions-range.html

SELECT "ips".* FROM "ips" WHERE (3065106743::bigint <@ iprange);
Run Code Online (Sandbox Code Playgroud)


Der*_*rek 5

我参加这个聚会有点晚了,但这对我来说非常有效。

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

使用该列中的范围填充表后,您所需要做的就是创建 GIST 索引:

CREATE INDEX ip_zip_ip4_range ON ip_zip USING gist (ip4_range);
Run Code Online (Sandbox Code Playgroud)

我的数据库中有近 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)
Run Code Online (Sandbox Code Playgroud)