可以查询多个 IP 地址。我们可以采取几种方法。假设range_start 和range_end 被定义为整数类型。
对于合理数量的 IP 地址,我们可以使用内联视图:
SELECT i.ip, a.*
FROM ( SELECT 3232235520 AS ip
UNION ALL SELECT 3232235521
UNION ALL SELECT 3232235522
UNION ALL SELECT 3232235523
UNION ALL SELECT 3232235524
UNION ALL SELECT 3232235525
) i
LEFT
JOIN ip_to_asn a
ON a.range_start <= i.ip
AND a.range_end >= i.ip
ORDER BY i.ip
这种方法适用于合理数量的 IP 地址。可以使用更多 UNION ALL SELECT 扩展内联视图以添加额外的 IP 地址。但这并不一定适用于“巨大”的数字。
当我们变得“巨大”时,我们将在 MySQL 中遇到限制... SQL 语句的最大大小受max_allowed_packet 限制,可能会出现SELECT 的数量限制.
内联视图可以替换为先构建的临时表。
DROP TEMPORARY TABLE IF EXISTS _ip_list_;
CREATE TEMPORARY TABLE _ip_list_ (ip BIGINT NOT NULL PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO _ip_list_ (ip) VALUES (3232235520),(3232235521),(3232235522),...;
...
INSERT INTO _ip_list_ (ip) VALUES (3232237989),(3232237990);
然后引用临时表代替内联视图:
SELECT i.ip, a.*
FROM _ip_list_ i
LEFT
JOIN ip_to_asn a
ON a.range_start <= i.ip
AND a.range_end >= i.ip
ORDER BY i.ip ;
然后删除临时表:
DROP TEMPORARY TABLE IF EXISTS _ip_list_ ;
其他一些注意事项:
搅动数据库连接会降低性能。建立和断开连接会产生大量开销。如果应用程序重复连接和断开连接,如果它对每个发出的 SQL 语句都这样做,那么这种开销会很明显。
并且运行单个 SQL 语句也有开销...该语句必须发送到服务器,语句解析语法,从语义评估,选择执行计划,执行计划,准备结果集,返回结果集到客户端。这就是为什么处理 set 比处理 row 更有效的原因。与将语句发送到数据库并让它一举处理集合相比,处理 RBAR(逐行处理)可能非常慢。
但有一个权衡。有了巨大的个集合,事情可能又开始变慢了。
即使您可以在每条语句中处理两个 IP 地址,这减半 需要执行的语句数。如果您在每条语句中使用 20 个 IP 地址,则可以将语句数量减少到每次需要一行的数量的 5%。
并且已经在(range_start,range_end) 上定义的复合索引适用于此查询。
跟进
正如 Rick James 在评论中指出的那样,我之前所说的“适当”的索引并不理想。
我们可以稍微不同地编写查询,这样可以更有效地利用该索引。
如果 (range_start,range_end) 是 UNIQUE(或 PRIMARY)KEY,那么即使存在“重叠”范围,也会为每个 IP 地址返回一行。 (前面的查询将返回所有 range_start 和 range_end 与 IP 地址重叠的行。)
SELECT t.ip, a.*
FROM ( SELECT s.ip
, s.range_start
, MIN(e.range_end) AS range_end
FROM ( SELECT i.ip
, MAX(r.range_start) AS range_start
FROM _ip_list_ i
LEFT
JOIN ip_to_asn r
ON r.range_start <= i.ip
GROUP BY i.ip
) s
LEFT
JOIN ip_to_asn e
ON e.range_start = s.range_start
AND e.range_end >= s.ip
GROUP BY s.ip, s.range_start
) t
LEFT
JOIN ip_to_asn a
ON a.range_start = t.range_start
AND a.range_end = t.range_end
ORDER BY t.ip ;
通过这个查询,对于最内层的内联视图查询s,优化器或许能够有效地利用前导列range_start的索引,快速识别出range_start的“最高”值(即小于或等于 IP 地址)。但是有了外部连接,以及i.ip 上的 GROUP BY,我真的需要查看 EXPLAIN 输出;这只是猜测优化器可能会做什么;重要的是优化器实际上做了什么。)
然后,对于内联视图查询e,MySQL 可能能够更有效地利用(range_start,range_end) 上的复合索引,因为第一列上的相等谓词,以及 MIN 聚合上的不相等条件第二列。
对于最外层的查询,由于两列的相等谓词,MySQL 肯定能够有效地利用复合索引。
这种形式的查询可能会显示改进的性能,或者性能可能会在手篮中陷入困境。 EXPLAIN 的输出应该可以很好地指示正在发生的事情。我们希望在 Extra 列中看到“Using index for group-by”,而我们只想在最外层的查询中看到 ORDER BY 的“Using filesort”。 (如果我们删除 ORDER BY 子句,我们不希望在 Extra 列中看到“Using filesort”。)
另一种方法是利用 SELECT 列表中的相关子查询。当结果集包含大量行时,相关子查询的执行可能会变得很昂贵。但是这种方法可以为某些用例提供令人满意的性能。
此查询依赖ip_to_asn 表中没有重叠范围,当存在重叠范围时,此查询不会产生预期结果。
SELECT t.ip, a.*
FROM ( SELECT i.ip
, ( SELECT MAX(s.range_start)
FROM ip_to_asn s
WHERE s.range_start <= i.ip
) AS range_start
, ( SELECT MIN(e.range_end)
FROM ip_to_asn e
WHERE e.range_end >= i.ip
) AS range_end
FROM _ip_list_ i
) r
LEFT
JOIN ip_to_asn a
ON a.range_start = r.range_start
AND a.range_end = r.range_end
为了说明为什么重叠范围会成为这个查询的问题,给出一个完全愚蠢的虚构示例
range_start range_end
----------- ---------
.101 .160
.128 .244
给定 IP 地址 .140,MAX(range_start) 子查询将找到 .128,MIN(range_end) 子查询将找到 .160,然后外部查询将尝试找到匹配行 range_start=.128 AND range_end=.160。而那一行根本不存在。