【问题标题】:IP address numbers in MySQL subqueryMySQL子查询中的IP地址编号
【发布时间】:2010-06-17 14:12:43
【问题描述】:

我在涉及存储在 MySQL (MySQL 5.0) 中的 IPV4 地址的子查询时遇到问题。

IP 地址存储在两个表中,均采用网络编号格式 - 例如MySQL 的 INET_ATON() 输出的格式。第一个表('events')包含许多行,其中包含与其关联的 IP 地址,第二个表('network_providers')包含给定网络块的提供者信息列表。

事件表(~4,000,000 行):

event_id (int)
event_name (varchar)
ip_address (unsigned int)

network_providers 表(约 60,000 行):

ip_start (unsigned int)
ip_end  (unsigned int)
provider_name (varchar)

针对我遇到的问题进行了简化,目标是按照以下方式创建导出:

event_id,event_name,ip_address,provider_name

如果按照以下任一方式进行查询,我会得到我期望的结果:

SELECT provider_name FROM network_providers WHERE INET_ATON('192.168.0.1') >= network_providers.ip_start ORDER BY network_providers.ip_start DESC LIMIT 1

SELECT provider_name FROM network_providers WHERE 3232235521 >= network_providers.ip_start ORDER BY network_providers.ip_start DESC LIMIT 1

也就是说,对于我查找的任何 IP,它都会返回正确的 provider_name(当然,我在查询中并没有真正使用 192.168.0.1)。

但是,当执行与子查询相同的查询时,按照以下方式,它不会产生我期望的结果:

SELECT 
events.event_id,
events.event_name,
    (SELECT provider_name FROM network_providers 
    WHERE events.ip_address >= network_providers.ip_start 
    ORDER BY network_providers.ip_start DESC LIMIT 1) as provider
FROM events

返回的是 provider 的不同(不正确)值。 provider 列中返回的超过 90%(但奇怪的是不是全部)的值包含该 IP 的错误提供商信息。

在子查询中使用 events.ip_address 只是为了回显该值,以确认它包含我期望的值并且子查询可以解析它。用实际的网络号替换 events.ip_address 也可以,只是在子查询中以这种对我不起作用的方式动态使用它。

我怀疑问题在于 MySQL 中的子查询有一些基本而重要的东西,我不明白。我以前在 MySQL 中使用过类似的 IP 地址,但以前没有使用子查询对它们进行过查找。

问题:

我非常感谢我如何获得我想要的输出的一个例子,如果有人知道,一些关于为什么我正在做的事情不起作用的启示,这样我就可以避免再次犯这个错误。

注意事项:

我尝试做的实际实际使用要复杂得多(涉及连接两个或三个表)。这是一个简化版本,以避免问题过于复杂。

此外,我知道我没有在 ip_start 和 ip_end 上使用 between - 这是故意的(数据库可能已过时,在这种情况下,数据库中的所有者几乎总是在下一个指定范围内并且“最佳猜测” ' 在这种情况下很好)但是我很感谢与该问题相关的任何改进建议。

效率总是很好,但在这种情况下绝对不是必需的 - 任何帮助表示赞赏。

【问题讨论】:

  • 我认为由于您的隐式连接(可能这是错误的术语,但表 正在 在这里连接,所以出现了可怕的笛卡尔积(或其子集)。 ..)

标签: sql mysql subquery


【解决方案1】:

你应该看看这篇文章:

http://jcole.us/blog/archives/2007/11/24/on-efficiently-geo-referencing-ips-with-maxmind-geoip-and-mysql-gis/

它有一些很好的想法,可以在与您的查询非常相似的查询中使用 IP。

您应该尝试的另一件事是使用存储函数而不是子查询。这将简化您的查询,如下所示:

SELECT 
event.id,
event.event_name,
GET_PROVIDER_NAME(event.ip_address) as provider
FROM events

【讨论】:

  • +1 该 URL 并不能完全解决我遇到的问题,但它很有趣(实际上与我正在做的事情非常相关,尽管我没有在我的问题中提到这一点想保持简单)。但是,您关于存储函数的观点一针见血。
【解决方案2】:

似乎没有办法通过 JOIN 或子查询来实现我想要的。

为了扩展 Ike Walker 关于使用存储函数的建议,我最终在 MySQL 中创建了一个存储函数,如下所示:

DELIMITER //
DROP FUNCTION IF EXISTS get_network_provider //
CREATE FUNCTION get_network_provider(ip_address_number INT) RETURNS VARCHAR(255)
BEGIN
DECLARE network_provider VARCHAR(255);
    SELECT provider_name INTO network_provider FROM network_providers
    WHERE ip_address_number >= network_providers.ip_start
    AND network_providers.provider_name != ""
    ORDER BY provider_name.ip_start DESC LIMIT 1;
RETURN network_provider;
END //

说明:

检查忽略空白名称,并为 ip_start 使用 >= & ORDER BY 而不是 BETWEEN ip_start 和 ip_end 是我正在使用的两个组合网络提供商数据库的特定软糖,这两个数据库都需要在此查询方式。

当调用函数的查询只需要返回几百个结果(尽管可能需要几秒钟)时,这种方法很有效。对于返回几千个结果的查询,可能需要 2 或 3 分钟。对于具有数万个(或更多)结果的查询,它太慢而无法实际使用。

使用这样的存储函数并不意外(即返回的每个结果都会触发一个单独的查询),但我确实比我预期的更快地遇到了性能下降。

推荐:

这样做的结果是我需要接受数据结构不适合我的需求。一位朋友已经向我指出了这一点,这并不是我当时真正想听到的(因为我真的很想使用那个特定的 network_provider 数据库,因为表中的其他键对我有用,例如地理定位等)。

如果您最终尝试使用遵循类似可疑数据格式的任何 IP 提供商数据库(或实际上任何其他数据库),那么我只能建议它们不适合并且不值得尝试拼凑一些可以与他们一起工作的东西。

至少您需要重新格式化数据,以便它们可以可靠地与简单的 BETWEEN 语句一起使用(没有排序,也没有其他比较),这样您就可以将它与子查询(或 JOINS)一起使用 - 尽管它很可能是表明任何混乱的数据都可能不是那么可靠。

【讨论】:

    猜你喜欢
    • 2014-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    • 2022-12-15
    • 1970-01-01
    • 2013-07-18
    相关资源
    最近更新 更多