我正在使用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_num
和end_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_num
和end_ip_num
上添加综合索引。
答案 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列上添加复合索引。