2011-11-19 14 views
6

मैं टेबल में शामिल होने की तरह देखने का एक तेजी से मार्ग ढूंढने में मुद्दा हो रहा है में आईपी के टेबल के साथ शामिल हो एक बार में केवल एक आईपी पता से, मुझे entity_ip LEFT JOIN geo_ip (या समान/एनालॉग तरीका) की आवश्यकता है।GeoIP मेज MySQL

यह वही है मैं अब के लिए है (बहुभुज का उपयोग कर के रूप में http://jcole.us/blog/archives/2007/11/24/on-efficiently-geo-referencing-ips-with-maxmind-geoip-and-mysql-gis/ पर सलाह दी है):

mysql> EXPLAIN SELECT li.*, gi.country_code FROM entity_ip AS li 
-> LEFT JOIN geo_ip AS gi ON 
-> MBRCONTAINS(gi.`ip_poly`, li.`ip_poly`); 

+----+-------------+-------+------+---------------+------+---------+------+--------+-------+ 
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
+----+-------------+-------+------+---------------+------+---------+------+--------+-------+ 
| 1 | SIMPLE  | li | ALL | NULL   | NULL | NULL | NULL | 2470 |  | 
| 1 | SIMPLE  | gi | ALL | ip_poly_index | NULL | NULL | NULL | 155183 |  | 
+----+-------------+-------+------+---------------+------+---------+------+--------+-------+ 

mysql> SELECT li.*, gi.country_code FROM entity AS li LEFT JOIN geo_ip AS gi ON MBRCONTAINS(gi.`ip_poly`, li.`ip_poly`) limit 0, 20; 
20 rows in set (2.22 sec) 

कोई बहुभुज

mysql> explain SELECT li.*, gi.country_code FROM entity_ip AS li LEFT JOIN geo_ip AS gi ON li.`ip_num` >= gi.`ip_num_start` AND li.`ip_num` <= gi.`ip_num_end` LIMIT 0,20; 
+----+-------------+-------+------+---------------------------+------+---------+------+--------+-------+ 
| id | select_type | table | type | possible_keys    | key | key_len | ref | rows | Extra | 
+----+-------------+-------+------+---------------------------+------+---------+------+--------+-------+ 
| 1 | SIMPLE  | li | ALL | NULL      | NULL | NULL | NULL | 2470 |  | 
| 1 | SIMPLE  | gi | ALL | PRIMARY,geo_ip,geo_ip_end | NULL | NULL | NULL | 155183 |  | 
+----+-------------+-------+------+---------------------------+------+---------+------+--------+-------+ 

mysql> SELECT li.*, gi.country_code FROM entity_ip AS li LEFT JOIN geo_ip AS gi ON li.ip_num BETWEEN gi.ip_num_start AND gi.ip_num_end limit 0, 20; 
20 rows in set (2.00 sec) 

(खोज में पंक्तियों की अधिक संख्या पर - कोई अंतर नहीं है)

वर्तमान में मुझे इन प्रश्नों से कोई तेज प्रदर्शन नहीं मिल रहा है क्योंकि प्रति सेकंड 0.1 सेकंड मेरे लिए बहुत धीमा है।

क्या इसे तेज़ी से बनाने का कोई तरीका है?

+1

अंधेरे में शॉट: किसी भी मौके पर कि 'entity_ip'' ip_num' पर एक सूचकांक दूसरी क्वेरी की गति में सुधार करेगा? –

+0

ए को MySQL के अंदर करना होगा?अगर हम ip_num_start इलाज और के रूप में संबद्ध अंक ip_num_end, और डॉट्स भर में एक व्यापक लाइन की एक्स-coord के रूप में क्रमबद्ध तरीके से entity_ip.ip_num पढ़ना, झाडू लाइन एल्गोरिथ्म की अवधारणा आप एक तेजी से n-दर-मीटर बाईं से चलाने दे सकते हैं MySQL के अंदर शामिल हों। –

+0

लेखक के मामले के बारे में नहीं जानते, मेरे लिए (और कई लोग) mysql केवल समाधान देखना बहुत दिलचस्प होगा। – Oroboros102

उत्तर

6

इस दृष्टिकोण में कुछ स्केलेबिलिटी समस्याएं हैं (क्या आप शहर के विशिष्ट भू-डेटा डेटा पर जाने के लिए चुनना चाहिए), लेकिन डेटा के दिए गए आकार के लिए, यह काफी अनुकूलन प्रदान करेगा।

आप जिस समस्या का सामना कर रहे हैं वह प्रभावी ढंग से है कि MySQL रेंज-आधारित क्वेरी को बहुत अच्छी तरह अनुकूलित नहीं करता है। आदर्श रूप में आप "से अधिक" की बजाय किसी इंडेक्स पर एक सटीक ("=") लुक-अप करना चाहते हैं, इसलिए हमें आपके द्वारा उपलब्ध डेटा से एक इंडेक्स बनाने की आवश्यकता होगी। इस तरह एक मैच की तलाश करते समय MySQL के मूल्यांकन के लिए बहुत कम पंक्तियां होंगी।

ऐसा करने के लिए, मेरा सुझाव है कि आप एक लुक-अप तालिका बनाएं जो आईपी पते के पहले ऑक्टेट (= 1.2.3.4 से = 1) के आधार पर भौगोलिक स्थान तालिका को अनुक्रमित करे। विचार यह है कि प्रत्येक लुकअप के लिए आपको करना होगा, आप सभी भौगोलिक स्थान आईपी को अनदेखा कर सकते हैं जो आपके द्वारा देखे जा रहे आईपी की तुलना में उसी ऑक्टेट से शुरू नहीं होते हैं।

CREATE TABLE `ip_geolocation_lookup` (
    `first_octet` int(10) unsigned NOT NULL DEFAULT '0', 
    `ip_numeric_start` int(10) unsigned NOT NULL DEFAULT '0', 
    `ip_numeric_end` int(10) unsigned NOT NULL DEFAULT '0', 
    KEY `first_octet` (`first_octet`,`ip_numeric_start`,`ip_numeric_end`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

इसके बाद, हम अपने भौगोलिक स्थान तालिका में डेटा उपलब्ध लेने के लिए और डेटा है कि शामिल किया गया उत्पादन की जरूरत है सभी (प्रथम) ओक्टेट्स जियोलोकेशन पंक्ति में शामिल हैं: आप ip_start = '5.3.0.0' और ip_end = '8.16.0.0', लुकअप तालिका के साथ एक प्रविष्टि है, तो ओक्टेट्स 5, 6, 7, और 8 तो के लिए पंक्तियों की आवश्यकता होगी ...

ip_geolocation 
|ip_start  |ip_end   |ip_numeric_start|ip_numeric_end| 
|72.255.119.248 |74.3.127.255 |1224701944  |1241743359 | 

परिवर्तित करना चाहिए रहे हैं:

ip_geolocation_lookup 
|first_octet|ip_numeric_start|ip_numeric_end| 
|72   |1224701944  |1241743359 | 
|73   |1224701944  |1241743359 | 
|74   |1224701944  |1241743359 | 

के बाद से किसी को यहाँ एक देशी MySQL समाधान के लिए अनुरोध किया है, यहाँ एक संग्रहीत प्रक्रिया है कि आप के लिए कि डेटा उत्पन्न करता रहेगा:

DROP PROCEDURE IF EXISTS recalculate_ip_geolocation_lookup; 

CREATE PROCEDURE recalculate_ip_geolocation_lookup() 
BEGIN 
    DECLARE i INT DEFAULT 0; 

    DELETE FROM ip_geolocation_lookup; 

    WHILE i < 256 DO 
     INSERT INTO ip_geolocation_lookup (first_octet, ip_numeric_start, ip_numeric_end) 
       SELECT i, ip_numeric_start, ip_numeric_end FROM ip_geolocation WHERE 
       (ip_numeric_start & 0xFF000000) >> 24 <= i AND 
       (ip_numeric_end & 0xFF000000) >> 24 >= i; 

     SET i = i + 1; 
    END WHILE; 
END; 

और फिर आपको लगता है कि संग्रहीत प्रक्रिया को फोन करके तालिका पॉप्युलेट करने होंगे:

CALL recalculate_ip_geolocation_lookup(); 

इस बिंदु पर आप जिस प्रक्रिया को अभी बनाया है उसे हटा सकते हैं - अब तक इसकी आवश्यकता नहीं है, जब तक आप लुक-अप तालिका को पुन: गणना नहीं करना चाहते।

लुक-अप तालिका होने के बाद, आपको बस इतना करना है कि इसे अपने प्रश्नों में एकीकृत करें और सुनिश्चित करें कि आप पहले ऑक्टेट से पूछताछ कर रहे हैं।लुक-अप तालिका में आपकी क्वेरी दो शर्तों को पूरा करेगा:

  1. सभी पंक्तियों जो आपके आईपी पते
  2. कि सबसेट के की पहली ओकटेट से मेल खोजें: पंक्ति जो सीमा से मेल खाता है है का पता लगाएं आपका आईपी पता

क्योंकि चरण दो डेटा के सबसेट पर किया जाता है, यह पूरे डेटा पर रेंज परीक्षण करने से काफी तेज है। यह अनुकूलन रणनीति की कुंजी है।

आईपी पते का पहला ऑक्टेट क्या है यह जानने के कई तरीके हैं; मैं (r.ip_numeric & 0xFF000000) >> 24 इस्तेमाल किया के बाद से मेरे स्रोत आईपी अंकीय रूप में कर रहे हैं:

SELECT 
    r.*, 
    g.country_code 
FROM 
    ip_geolocation g, 
    ip_geolocation_lookup l, 
    ip_random r 
WHERE 
    l.first_octet = (r.ip_numeric & 0xFF000000) >> 24 AND 
    l.ip_numeric_start <= r.ip_numeric AND  
    l.ip_numeric_end >= r.ip_numeric AND 
    g.ip_numeric_start = l.ip_numeric_start; 

अब, बेशक मैं एक छोटे से अंत में आलसी प्राप्त किया था: आप आसानी से पूरी तरह ip_geolocation तालिका से छुटकारा पाने सकता है अगर आप ip_geolocation_lookup टेबल भी शामिल किए गए देश डेटा मैं इस क्वेरी से एक टेबल छोड़ने का अनुमान लगा रहा हूं, यह थोड़ा तेज़ कर देगा।

और, अंत में, संदर्भ के लिए इस प्रतिक्रिया में उपयोग की जाने वाली दो अन्य तालिकाएं हैं, क्योंकि वे आपकी तालिकाओं से अलग हैं। मुझे यकीन है कि वे संगत हैं, यद्यपि।

# This table contains the original geolocation data 

CREATE TABLE `ip_geolocation` (
    `ip_start` varchar(16) NOT NULL DEFAULT '', 
    `ip_end` varchar(16) NOT NULL DEFAULT '', 
    `ip_numeric_start` int(10) unsigned NOT NULL DEFAULT '0', 
    `ip_numeric_end` int(10) unsigned NOT NULL DEFAULT '0', 
    `country_code` varchar(3) NOT NULL DEFAULT '', 
    `country_name` varchar(64) NOT NULL DEFAULT '', 
    PRIMARY KEY (`ip_numeric_start`), 
    KEY `country_code` (`country_code`), 
    KEY `ip_start` (`ip_start`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 


# This table simply holds random IP data that can be used for testing 

CREATE TABLE `ip_random` (
    `ip` varchar(16) NOT NULL DEFAULT '', 
    `ip_numeric` int(10) unsigned NOT NULL DEFAULT '0', 
    PRIMARY KEY (`ip`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
+0

वाह, extremly विस्तृत जवाब। कृपया, इस दृष्टिकोण का परीक्षण करने के लिए मुझे दिन की एक जोड़ी दें। लगता है, काम समाधान की तरह। – Oroboros102

+0

इस क्वेरी बहुत तेजी से, fullscan से है, लेकिन अभी भी यह (ranges_qty/255) बहुत सी पंक्तियाँ स्कैन करने के लिए की जरूरत है। यदि हम भू-आईपी प्रति शहर रेंज टेबल (30 000 000 पंक्तियों) का उपयोग करेंगे, तो यह क्वेरी धीमी हो जाएगी। मुझे कुछ soluton मिला, जो ज्यामिति का उपयोग करता है। यदि एक मेरे सवाल का कोई भी जवाब apropriate (http://stackoverflow.com/questions/8244535/joins-on-spatial-mysql-indexes) मिल जाएगा, मैं इस प्रश्न के लिए बेहतर समाधान होगा। यदि नहीं - आपका उत्तर सबसे अच्छा होगा। – Oroboros102

+0

सवाल वास्तव में अलग था। अंदरूनी जॉइन ठीक काम करता है, जबकि बाएं जॉइन में 2k entity_ip तालिका पर कम से कम 4 मिनट लगेंगे। –

0

बस वापस समुदाय को देना चाहता था:

DROP PROCEDURE IF EXISTS recalculate_ip_geolocation_lookup; 

DELIMITER ;; 
CREATE PROCEDURE recalculate_ip_geolocation_lookup() 
BEGIN 
    DECLARE i INT DEFAULT 0; 
DROP TABLE `ip_geolocation_lookup`; 

CREATE TABLE `ip_geolocation_lookup` (
    `first_octet` smallint(5) unsigned NOT NULL DEFAULT '0', 
    `startIpNum` int(10) unsigned NOT NULL DEFAULT '0', 
    `endIpNum` int(10) unsigned NOT NULL DEFAULT '0', 
    `locId` int(11) NOT NULL, 
    PRIMARY KEY (`first_octet`,`startIpNum`,`endIpNum`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

INSERT IGNORE INTO ip_geolocation_lookup 
SELECT startIpNum DIV 1048576 as first_octet, startIpNum, endIpNum, locId 
FROM ip_geolocation; 

INSERT IGNORE INTO ip_geolocation_lookup 
SELECT endIpNum DIV 1048576 as first_octet, startIpNum, endIpNum, locId 
FROM ip_geolocation; 

    WHILE i < 1048576 DO 
    INSERT IGNORE INTO ip_geolocation_lookup 
     SELECT i, startIpNum, endIpNum, locId 
     FROM ip_geolocation_lookup 
     WHERE first_octet = i-1 
     AND endIpNum DIV 1048576 > i; 
    SET i = i + 1; 
    END WHILE; 
END;; 
DELIMITER ; 

CALL recalculate_ip_geolocation_lookup(); 

यह तरीका उनके समाधान की तुलना में तेजी बनाता है और अधिक नीचे अभ्यास:

यहाँ Aleksi के समाधान के एक और भी बेहतर और अनुकूलित रास्ता इमारत है आसानी से क्योंकि हम केवल पहले 8 नहीं ले रहे हैं, लेकिन पहले 20 बिट्स हैं। प्रदर्शन में शामिल हों: 158ms में 100000 पंक्तियां। आपको अपने संस्करण में तालिका और फ़ील्ड नामों का नाम बदलना पड़ सकता है।

क्वेरी

SELECT ip, kl.* 
FROM random_ips ki 
JOIN `ip_geolocation_lookup` kb ON (ki.`ip` DIV 1048576 = kb.`first_octet` AND ki.`ip` >= kb.`startIpNum` AND ki.`ip` <= kb.`endIpNum`) 
JOIN ip_maxmind_locations kl ON kb.`locId` = kl.`locId`; 
1

का उपयोग करके अभी तक टिप्पणी नहीं कर सकता, लेकिन user1281376 के जवाब गलत है और काम नहीं करता। कारण आप केवल पहले ऑक्टेट का उपयोग करते हैं क्योंकि आप अन्य आईपी श्रेणियों से मेल नहीं खाते हैं। वहां कई श्रेणियां हैं जो कई दूसरे ऑक्टेट्स का विस्तार करती हैं जो उपयोगकर्ता 1281376 के बदले गए क्वेरी से मेल नहीं खाती हैं। और हाँ, यह वास्तव में होता है यदि आप मैक्समिंड जियोआईपी डेटा का उपयोग करते हैं।

एलेक्सिस सुझाव के साथ आप फिटर ऑक्टेट पर एक साधारण तुलना कर सकते हैं, इस प्रकार मिलान सेट को कम कर सकते हैं।

+0

शायद मुझे इसकी जांच करनी चाहिए थी लेकिन उस समय मैंने इसे छोड़ने का फैसला किया क्योंकि यह वैसे भी काम कर रहा था (मुझे याद है कि मैंने यह भी माना है कि लेखक ने अपना होमवर्क किया है)। धन्यवाद –

+0

सही, यह स्पष्ट रूप से तेज़ है, हालांकि विशेष रूप से maxmind की geoip तालिका के साथ आप उदाहरण के लिए स्तर 3 से मेल नहीं खाएंगे। मुझे पहली बार पता चला कि मैं इसमें भाग गया था। तो आपको end_range के लिए एक और पंक्ति जोड़नी होगी और फिर भी आप एक श्रेणी क्वेरी के साथ अटक गए हैं। और जब भी आपके पास आईपी के लिए कोई मिलान नहीं है, तब भी यह बदतर हो जाएगा, यह पूरी तालिका को स्कैन करेगा। – knrdk

0

मुझे एक आसान तरीका मिला।

SELECT * FROM YOUR_TABLE AS A 
LEFT JOIN ip_geo_index AS B ON B._ip = A._ip DIV 256 
LEFT JOIN ip_geo AS C ON C.ipStart = B.ipStart; 
: मुझे लगता है कि समूह% के सभी पहले आईपी 256 = 0, तो हम एक ip_index तालिका

CREATE TABLE `t_map_geo_range` (
    `_ip` int(10) unsigned NOT NULL, 
    `_ipStart` int(10) unsigned NOT NULL, 
    PRIMARY KEY (`_ip`) 
) ENGINE=MyISAM 

कैसे सूचकांक तालिका

FOR_EACH(Every row of ip_geo) 
{ 
    FOR(Every ip FROM ipGroupStart/256 to ipGroupEnd/256) 
    { 
     INSERT INTO ip_geo_index(ip, ipGroupStart); 
    } 
} 

का उपयोग कैसे करें भरने के लिए जोड़ सकते हैं देखा

1000 से अधिक बार तेज़।

+0

कृपया ऊपर उत्तर देखें। –

संबंधित मुद्दे