因此,我有一個類似於45.76.255.14的IP,並且我有一個CIDR行作爲單個varchar存儲的表, 我將如何選擇在該IP範圍內的CIDR地址。例如45.76.255.14/31選擇在IP範圍內的CIDR
所以理論上:凡在範圍內的IP
的因此,我有一個類似於45.76.255.14的IP,並且我有一個CIDR行作爲單個varchar存儲的表, 我將如何選擇在該IP範圍內的CIDR地址。例如45.76.255.14/31選擇在IP範圍內的CIDR
所以理論上:凡在範圍內的IP
的貯藏的IP地址是不是將它們存儲的最優化的方式,因爲點分四組是一個32位無符號整數的人類友好的表示,不借給自己的數據庫indexi NG。但有時它從根本上更方便,並且在小規模情況下,查詢需要表掃描通常不是問題。
MySQL存儲功能是封裝的背後,可以在查詢中引用的簡單功能比較複雜的邏輯的一個很好的方式,可能導致更易於理解的查詢和縮小複製/粘貼錯誤。
所以,這裏有一個存儲功能我編寫的名爲find_ip4_in_cidr4()
。它的作用與內置函數FIND_IN_SET()
有些類似 - 你給它一個值,你給它一個「set」(CIDR spec),它返回一個值來表示值是否在集合中。
首先,函數的作用的說明圖:
如果地址塊內,則返回前綴長度。爲什麼返回前綴長度?非零整數是「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)
有一個爲全0地址,我們返回-1的特殊情況(仍是 「真」,但保留排序順序):
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)
現在,德codez:
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
中的函數時,服務器無法通過函數「向後看」使用索引來優化查詢。
謝謝,我不存儲IPs,我只存儲CIDR並將輸入與數據庫匹配。這解釋了很多。 – RumbleFrog
助攻這個問題的選擇CIDR:MySQL query to convert CIDR into IP range
下面是對我的作品的解決方案:
SELECT
`cidr`
FROM
cidr_list
WHERE
INET_ATON('IP') BETWEEN(
INET_ATON(SUBSTRING_INDEX(`cidr`, '/', 1)) & 0xffffffff ^(
(
0x1 <<(
32 - SUBSTRING_INDEX(`cidr`, '/', -1)
)
) -1
)
) AND(
INET_ATON(SUBSTRING_INDEX(`cidr`, '/', 1)) |(
(
0x100000000 >> SUBSTRING_INDEX(`cidr`, '/', -1)
) -1
)
)
在一個
VARCHAR
點分四組表示法
你並不需要'BETWEEN'測試,因爲你真正需要評估的是網絡和掩碼==地址和掩碼。掩碼本身通過掩蓋無關位來提供「之間」。 –
@ Michael-sqlbot,你建議我用什麼來代替? – RumbleFrog
你在做什麼沒什麼問題,我只是說你不必做結束範圍計算,因爲這是網絡掩碼的要點。我使用存儲函數來封裝邏輯。 –
它沒有任何意義..葉問題不是一個範圍。一個CIDR可以... – Dekel
@Dekel,你是正確的,一個IP是不是一個範圍,但我請求獲取是在IP範圍CIDRs,又名包括IP – RumbleFrog
從理論上講:'選擇*來自cidrlist,其中@ip位於startip(cidr)和endip(cidr)之間。實際上,這取決於你如何存儲你的數據。如果你將範圍存儲爲varchars('45.76.255。14/31'),比起存儲它們作爲範圍的開始和結束的整數,比較難。但是因爲你沒有給我們你的數據模型,所以理論應該足夠了。提示:實際上,MySQL具有將ip字符串(不帶子網)轉換爲int:'INET_ATON()'的功能。根據您的數據,您可能需要編寫像'startip()'/'endip()'這樣的函數來轉換範圍。 – Solarflare