PostgreSQL网络地址范围查询优化

问题描述 投票:0回答:4

下面是大约600万条记录的表结构:

CREATE TABLE "ip_loc" (
  "start_ip" inet,
  "end_ip" inet,
  "iso2" varchar(4),
  "state" varchar(100),
  "city" varchar(100) 
);

CREATE INDEX "index_ip_loc" on ip_loc using gist(iprange(start_ip,end_ip));

查询大约需要1秒。

EXPLAIN ANALYZE select * from ip_loc where iprange(start_ip,end_ip)@>'180.167.1.25'::inet;

Bitmap Heap Scan on ip_loc (cost=1080.76..49100.68 rows=28948 width=41) (actual time=1039.428..1039.429 rows=1 loops=1)
  Recheck Cond: (iprange(start_ip, end_ip) @> '180.167.1.25'::inet)
  Heap Blocks: exact=1
  ->  Bitmap Index Scan on index_ip_loc (cost=0.00..1073.53 rows=28948 width=0) (actual time=1039.411..1039.411 rows=1 loops=1)
        Index Cond: (iprange(start_ip, end_ip) @> '180.167.1.25'::inet) Planning time: 0.090 ms Execution time: 1039.466 ms

iprange是自定义类型:

CREATE TYPE iprange AS RANGE (
    SUBTYPE = inet
);

有没有办法可以更快地进行查询?

database postgresql query-optimization
4个回答
0
投票

inet
类型是一种复合类型,而不是构造 IPv4 地址所需的简单 32 位;例如,它包括一个网络掩码。这使得存储、索引和检索变得不必要地复杂如果您感兴趣的是实际 IP 地址(即实际地址的 32 位,而不是带有网络掩码的地址,例如您从列出客户端的 Web 服务器获得的地址)应用程序的),并且您不会操纵数据库内的 IP 地址。如果是这种情况,您可以将
start_ip
end_ip
存储为简单整数,并使用简单整数比较对它们进行操作。 (可以使用
integer[4]
数据类型对 IPv6 地址执行相同操作。)

要记住的一点是默认范围构造函数行为是包含下限并排除上限因此在索引和查询中不包含实际的

end_ip

最后,如果您坚持使用范围类型,则应在索引上添加

range_ops
运算符类以获得最佳性能。


0
投票

这些范围不重叠?我会尝试 btree 索引

end_ip
并执行以下操作:

with candidate as (
  select * from ip_loc
  where end_ip<='38.167.1.53'::inet
  order by end_ip desc
  limit 1
)
select * from candidate
where start_ip<='38.167.1.53'::inet;

在我的计算机上处理 4M 行只需 0.1 毫秒。

记住在填充数据后分析表格。


0
投票

仅为end_ip添加聚集索引


0
投票

聚会有点晚了,但这就是解决方案。

  • 不要直接使用start_ip和end_ip。为什么?因为即使您在这两列上使用 和 索引,也不能保证 postgres 会利用该索引。如果查询规划器预期(实际上不是)返回太多行,它将使用行的顺序扫描,我们希望不惜一切代价避免这种情况

  • 相反,请使用一个预处理步骤:加载数据时,将两列合并到一个子网中,并将其存储在另一个 inet 列中。即使在创建表之后,这也很容易做到:

ALTER TABLE ip_loc ADD COLUMN subnet inet;
CREATE INDEX ON ip_loc USING gist (subnet inet_ops);
UPDATE ip_loc SET SUBNET = inet_merge(start_ip, end_ip);

注意在新列上创建索引,并使用神奇的

inet_merge
函数填充它

现在您的查询可以更改为查询“子网中包含的”而不是“两个 inets 之间”,从查询分析器的角度来看,预计返回的行数要少得多 - 因此它将使用我们上面创建的索引,导致查询速度更快,例如:

select * from ip_loc where '180.167.1.25'::inet << subnet;

使用上述方法,上述时间从大约 1 秒缩短到 10 毫秒以下

© www.soinside.com 2019 - 2024. All rights reserved.