我有2百万个IP地址和2500万个IP范围,其中包含存储在PostgreSQL中的Start IP,End IP和Geo-Locations。有没有一种有效的方法可以从2500万个数据库中查找这200万个IP的地理位置?我所做的是比较IP地址是否落在起始IP和结束IP之间,并查找相应的位置。然而,这似乎永远。据推测,这更像是从一组范围中查找一堆整数,例如搜索{7,13,31,42}:
Start End Loc
1 10 US
11 20 US
21 26 CN
29 32 SE
33 45 CA
并返回:
7 US
13 US
31 SE
42 CA
请注意,范围可能未必连接,且尺寸可能不同。谢谢!
修改
作为一个具体的例子,这是我正在处理的数据:
start_ip | end_ip | country | region | city |
------------------+------------------+---------+-----------+-----------+-
1.33.254.73/32 | 1.33.254.73/32 | jpn | 33 | kurashiki |
1.39.1.0/32 | 1.39.4.255/32 | ind | mh | mumbai |
1.40.144.0/32 | 1.40.145.255/32 | aus | ns | fairfield |
1.40.235.0/32 | 1.40.242.255/32 | aus | ns | sydney |
1.44.28.0/32 | 1.44.29.255/32 | aus | vi | melbourne |
1.44.82.0/32 | 1.44.83.255/32 | aus | vi | melbourne |
1.44.92.0/32 | 1.44.93.255/32 | aus | vi | melbourne |
1.44.128.0/32 | 1.44.129.255/32 | aus | vi | melbourne |
1.44.220.0/32 | 1.44.221.255/32 | aus | vi | melbourne |
......
......
查询类似于:
75.149.219.61/32
68.239.61.29/32
96.41.50.165/32
183.62.126.7/32
......
答案 0 :(得分:2)
我认为,最佳和更优雅的解决方案是存储IP和范围 作为inet格式。 IP范围通常以网络/掩码格式发布, 不是开始/结束。这允许编写基于JOIN的
ON (ip.addr << geoloc.range)
当然ip表应该由addr和geoloc索引(范围,位置), 如果你没有CIDR格式并且需要从开始/结束构建它,那可能很昂贵(但是,之后的表会更容易使用)。
见
http://www.postgresql.org/docs/9.0/static/functions-net.html
编辑:不幸的是,这些开始/结束值看起来像“优化的”CIDR范围。换句话说,例如,
1.40.235.0 1.40.242.255
实际上是四个独立的连续范围的合并:
11101011 235.0-235.255
11101100 236.0-239.255
11101111
11110000 240.0-241.255
11110001
11110010 242.0-242.255
因此将行分解为CIDR操作所需的四行是不切实际的。
开始/结束看起来是cidr数据类型,所以将它们转换为inet(它们都是/ 32无论如何......)并保持查询的值也在inet数据类型中,索引在Start,End,应该给出合理的结果:
SELECT query.ip, geoloc.country, geoloc.region, geoloc.city
FROM query JOIN geoloc
ON (query.ip >= geoloc.start_ip AND query.ip <= geoloc.end_ip);
另一种选择,不是非常优雅(实际上是黑客),将基于例如“爆炸”ip和geoloc表。 addr和range的第一个字节分成不同的子表(我不希望你的IP范围与第一个字节不同)。
SELECT * FROM geoloc
WHERE start_ip >= inet '5.0.0.0' and end_ip <= inet '5.255.255.255'
INTO TABLE geoloc_5;
SELECT * FROM query
WHERE start_ip >= inet '5.0.0.0' and end_ip <= inet '5.255.255.255'
INTO TABLE query_5;
Remember to CREATE INDEX on geoloc_5 start_ip, end_ip
这种方法几年前已经工作了,对于一个大的PostgreSQL批处理,但我希望,从那时起,一个更聪明的索引管理器 - 连同专用数据类型 - 将演变为超过匹配这个DIY分区。因此,如果&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;无法使用CIDR运算符。
那就是说,假设两张桌子分布均匀(只是为了得到一个大概的数字)。
然后,在2M x 25M记录上,而不是一个SELECT,运行256个2M / 256的SELECT 25M / 256。因此,不是1 x 2M x 25M = 50 T,而是256 x 2M / 256 x 25M / 256 = 192G 比较,相对于直接JOIN,应该快约200倍。
但我再说一遍,我希望PostgreSQL看到一个正确索引的CIDR字段,将不再真正执行“直接”JOIN,而是使用这个技巧(然后一些)。
答案 1 :(得分:1)
如果您要查询Loc
列,则应该add an index。此外,由于这是一个3列表,因此将StartIP
和EndIP
组合使用可能是明智之举,将其用作键,并使用Geolocation
作为值,然后阅读此所有这些都来自键值存储,例如Redis或Memcached。 NoSQL /无表格数据存储是为这类事物而设计的,您可以在这里读取数百万个数据点。
答案 2 :(得分:1)
您必须提供有关此方面有意义输入的查询和查询计划。 E.g:
explain select hits.ip, locations.loc
from hits left outer join locations
on (hits.ip >= locations.start and hits.ip <= locations.stop);
QUERY PLAN
-------------------------------------------------------------------------------
Nested Loop Left Join (cost=0.00..245.06 rows=2400 width=36)
Join Filter: ((hits.ip >= locations.start) AND (hits.ip <= locations.stop))
-> Seq Scan on hits (cost=0.00..34.00 rows=2400 width=4)
-> Materialize (cost=0.00..1.07 rows=5 width=40)
-> Seq Scan on locations (cost=0.00..1.05 rows=5 width=40)
(5 rows)
我不确定您是否想要像您提供的其他答案之一那样向您的索引添加位置数据。这只是死数据增加膨胀,查找行没有用。
即使您使用支持仅索引扫描的pg版本(9.2,仍然处于测试阶段),较小的更精简的索引可能仍会提供更快的结果,每行一次额外的元组查找。