所以我有一个IP,例如45.76.255.14,我有一个表,其中CIDR行存储为单个varchar,
我选择了该IP地址范围内的CIDR。例如45.76.255.14/31
因此,从理论上讲:在IP范围内选择CIDR
最佳答案
将IP地址存储在VARCHAR
中不是最佳的存储方式,因为点对四位数是人类友好的32位无符号整数的表示形式,因此不适合数据库索引。但是有时从根本上来说更方便,并且在小规模情况下,查询需要进行表扫描这一事实通常不是问题。
MySQL存储函数是将相对复杂的逻辑封装在可以在查询中引用的简单函数后面的一种好方法,这可能导致更易于理解的查询并减少复制/粘贴错误。
因此,这是我写的称为find_ip4_in_cidr4()
的存储函数。它的工作方式与内置函数FIND_IN_SET()
相似-给它提供一个值,然后给它一个“集合”(CIDR规范),它返回一个值以指示该值是否在集合中。
首先,说明作用中的功能:
如果地址在块内,则返回前缀长度。为什么返回前缀长度?非零整数是“ true”,因此我们可以返回1
,但是如果要对匹配结果进行排序以找到多个匹配前缀中最短或最长的前缀,则可以ORDER BY
该函数的返回值。
mysql> SELECT find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24') |
+-----------------------------------------------------+
| 24 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16') |
+-----------------------------------------------------+
| 16 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
不在街区吗?返回0(假)。
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24') |
+-----------------------------------------------------+
| 0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24') |
+-----------------------------------------------------+
| 0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
全零地址有一种特殊情况,我们返回-1(仍然为“ true”,但保留排序顺序):
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0');
+------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0') |
+------------------------------------------------+
| -1 |
+------------------------------------------------+
1 row in set (0.00 sec)
废话参数返回null:
mysql> SELECT find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24') |
+-----------------------------------------------------+
| NULL |
+-----------------------------------------------------+
1 row in set (0.00 sec)
现在,编码:
DELIMITER $$
DROP FUNCTION IF EXISTS `find_ip4_in_cidr4` $$
CREATE DEFINER=`mezzell`@`%` FUNCTION `find_ip4_in_cidr4`(
_address VARCHAR(15),
_block VARCHAR(18)
) RETURNS TINYINT
DETERMINISTIC /* for a given input, this function always returns the same output */
CONTAINS SQL /* the function does not read from or write to tables */
BEGIN
-- given an IPv4 address and a cidr spec,
-- return -1 for a valid address inside 0.0.0.0/0
-- return prefix length if the address is within the block,
-- return 0 if the address is outside the block,
-- otherwise return null
DECLARE _ip_aton INT UNSIGNED DEFAULT INET_ATON(_address);
DECLARE _cidr_aton INT UNSIGNED DEFAULT INET_ATON(SUBSTRING_INDEX(_block,'/',1));
DECLARE _prefix TINYINT UNSIGNED DEFAULT SUBSTRING_INDEX(_block,'/',-1);
DECLARE _bitmask INT UNSIGNED DEFAULT (0xFFFFFFFF << (32 - _prefix)) & 0xFFFFFFFF;
RETURN CASE /* the first match, not "best" match is used in a CASE expression */
WHEN _ip_aton IS NULL OR _cidr_aton IS NULL OR /* sanity checks */
_prefix IS NULL OR _bitmask IS NULL OR
_prefix NOT BETWEEN 0 AND 32 OR
(_prefix = 0 AND _cidr_aton != 0) THEN NULL
WHEN _cidr_aton = 0 AND _bitmask = 0 THEN -1
WHEN _ip_aton & _bitmask = _cidr_aton & _bitmask THEN _prefix /* here's the only actual test needed */
ELSE 0 END;
END $$
DELIMITER ;
一个问题不是专门针对存储函数,而是适用于大多数RDBMS平台上的大多数函数,是当将列用作
WHERE
中函数的参数时,服务器无法通过该函数“向后看”使用索引来优化查询。关于mysql - 选择IP范围内的CIDR,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45656070/