2017-08-13 179 views
0

因此,我有一個類似於45.76.255.14的IP,並且我有一個CIDR行作爲單個varchar存儲的表, 我將如何選擇在該IP範圍內的CIDR地址。例如45.76.255.14/31選擇在IP範圍內的CIDR

所以理論上:凡在範圍內的IP

+0

它沒有任何意義..葉問題不是一個範圍。一個CIDR可以... – Dekel

+0

@Dekel,你是正確的,一個IP是不是一個範圍,但我請求獲取是在IP範圍CIDRs,又名包括IP – RumbleFrog

+0

從理論上講:'選擇*來自cidrlist,其中@ip位於startip(cidr)和endip(cidr)之間。實際上,這取決於你如何存儲你的數據。如果你將範圍存儲爲varchars('45.76.255。14/31'),比起存儲它們作爲範圍的開始和結束的整數,比較難。但是因爲你沒有給我們你的數據模型,所以理論應該足夠了。提示:實際上,MySQL具有將ip字符串(不帶子網)轉換爲int:'INET_ATON()'的功能。根據您的數據,您可能需要編寫像'startip()'/'endip()'這樣的函數來轉換範圍。 – Solarflare

回答

1

貯藏的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中的函數時,服務器無法通過函數「向後看」使用索引來優化查詢。

+0

謝謝,我不存儲IPs,我只存儲CIDR並將輸入與數據庫匹配。這解釋了很多。 – RumbleFrog

1

助攻這個問題的選擇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點分四組表示法
+0

你並不需要'BETWEEN'測試,因爲你真正需要評估的是網絡和掩碼==地址和掩碼。掩碼本身通過掩蓋無關位來提供「之間」。 –

+0

@ Michael-sqlbot,你建議我用什麼來代替? – RumbleFrog

+0

你在做什麼沒什麼問題,我只是說你不必做結束範圍計算,因爲這是網絡掩碼的要點。我使用存儲函數來封裝邏輯。 –