2016-04-15 102 views
0

我有一個包含名稱,電子郵件地址和IP的1500萬條記錄的表。我需要使用IP地址更新同一個表中國家代碼的另一列。我下載了一個包含所有IP範圍和相關國家的小型數據庫(ip2location lite - https://lite.ip2location.com/)。 ip2location表具有以下結構;如何優化此範圍查詢

CREATE TABLE `ip2location_db1` (
    `ip_from` int(10) unsigned DEFAULT NULL, 
    `ip_to` int(10) unsigned DEFAULT NULL, 
    `country_code` char(2) COLLATE utf8_bin DEFAULT NULL, 
    `country_name` varchar(64) COLLATE utf8_bin DEFAULT NULL, 
KEY `idx_ip_from` (`ip_from`), 
KEY `idx_ip_to` (`ip_to`), 
KEY `idx_ip_from_to` (`ip_from`,`ip_to`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin 

我使用以下函數從ip地址檢索國家代碼;

CREATE DEFINER=`root`@`localhost` FUNCTION `get_country_code`(
    ipAddress varchar(30) 
) RETURNS VARCHAR(2) 
    DETERMINISTIC 
    BEGIN 
     DECLARE ipNumber INT UNSIGNED; 
     DECLARE countryCode varchar(2); 
     SET ipNumber = SUBSTRING_INDEX(ipAddress, '.', 1) * 16777216; 
     SET ipNumber = ipNumber + (SUBSTRING_INDEX(SUBSTRING_INDEX(ipAddress, '.', 2),'.',-1) * 65536); 
     SET ipNumber = ipNumber + (SUBSTRING_INDEX(SUBSTRING_INDEX(ipAddress, '.', -2),'.',1) * 256); 
     SET ipNumber = ipNumber + SUBSTRING_INDEX(ipAddress, '.', -1); 

     SET countryCode = 
      (SELECT  country_code 
      FROM  ip2location.ip2location_db1 
      USE INDEX (idx_ip_from_to) 
      WHERE  ipNumber >= ip2location.ip2location_db1.ip_from AND ipNumber <= ip2location.ip2location_db1.ip_to 
      LIMIT  1); 

     RETURN countryCode; 
    END$$ 
DELIMITER ; 

我已經運行了EXPLAIN語句,這是輸出;

'1', 'SIMPLE', 'ip2location_db1', NULL, 'range', 'idx_ip_from_to', 'idx_ip_from_to', '5', NULL, '1', '33.33', 'Using index condition' 

我的問題是,1000條記錄查詢採用15S〜執行這意味着運行在所有數據庫中的相同的查詢將需要超過2天就可以完成。有沒有辦法來改善這個查詢。

PS - 如果我刪除了USE INDEX(idx_ip_from_to),查詢需要兩倍的時間。你能解釋爲什麼嗎?

而且我不是一個數據庫專家,所以容忍我:)

+0

表格是否有重疊範圍?如果是這樣,你不能優化它(即使戈登的建議)。 –

+0

不要對'country_code'使用'utf8' - 只需要2個時需要6個字節;使用'ascii'。 –

+0

IPv6怎麼樣? –

回答

0

這可能是相當棘手。我認爲問題是隻有ip_from部分條件可以使用。看是否有此得到表現你想要的:

SET countryCode = 
     (SELECT  country_code 
     FROM  ip2location.ip2location_db1 l 
     WHERE  ipNumber >= l.ip_from 
     ORDER BY ip_to 
     LIMIT  1 
     ); 

我知道我要走斷ip_to。如果這樣做,那麼你可以做兩個部分的全面檢查。首先使用類似的查詢獲得ip_from。然後使用等式查詢來獲取行中其餘的信息。

+0

謝謝...現在就試試 – claytonc

0

USE INDEX幫助的原因是因爲MySQL不打算使用該索引。它的優化器選擇了另一個,但它猜錯了。有時會發生。

此外,我不確定這是否會影響性能噸,但您應該只使用INET_ATON將IP地址字符串更改爲一個整數。您不需要SUBSTRING_INDEX業務,而且速度可能會更慢。

我會做什麼這裏是測量從和之間的最大距離:

SELECT MAX(ip_from - ip_to) AS distance 
FROM ip2location_db1; 

假設這不是一個愚蠢的號碼,您將能夠正常使用ip_from指數。支票就變成了:

WHERE ipNumber BETWEEN ip_from AND ip_from + distance 
    AND ipNumber <= ip_to 

這裏的目標是讓所有的信息來找到一個狹窄的一套行來自一個列的值的範圍有限:ip_from。然後ip_to只是一個準確性檢查。

你想這樣做的原因是因爲ip_to值(索引的第二部分)只有在找到相應的ip_from值後才能使用。所以它仍然需要掃描大部分索引記錄以獲得ip_from的低值,而沒有上限。


否則,您可能會考慮測量您的1500萬條記錄中IP地址的唯一性。例如,如果只有500萬個唯一的IP,則最好提取唯一列表,將它們映射到國家/地區代碼,然後使用該映射(在運行時或更新原始表)。取決於。

如果值是本地化的集羣非常獨特的,但可能,你可以嘗試從ip2location_db1,甚至水平分區刪除無關行以提高範圍檢查。我不確定這會贏得什麼,但是如果您可以在原始表格上使用某些索引來僅諮詢特定分區,那麼您可能會贏得一些性能。