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_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 );
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_num和end_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的当前统计信息来选择适当的查询计划.
我在来自 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)
我参加这个聚会有点晚了,但这对我来说非常有效。
考虑安装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)