2011-11-19 50 views
6

Tôi gặp một vấn đề tìm kiếm một cách nhanh chóng gia nhập các bảng tìm kiếm như thế:bảng GeoIP tham gia với bảng IP trong MySQL

mysql> explain geo_ip; 
+--------------+------------------+------+-----+---------+-------+ 
| Field  | Type    | Null | Key | Default | Extra | 
+--------------+------------------+------+-----+---------+-------+ 
| ip_start  | varchar(32)  | NO |  | ""  |  | 
| ip_end  | varchar(32)  | NO |  | ""  |  | 
| ip_num_start | int(64) unsigned | NO | PRI | 0  |  | 
| ip_num_end | int(64) unsigned | NO |  | 0  |  | 
| country_code | varchar(3)  | NO |  | ""  |  | 
| country_name | varchar(64)  | NO |  | ""  |  | 
| ip_poly  | geometry   | NO | MUL | NULL |  | 
+--------------+------------------+------+-----+---------+-------+ 


mysql> explain entity_ip; 
+------------+---------------------+------+-----+---------+-------+ 
| Field  | Type    | Null | Key | Default | Extra | 
+------------+---------------------+------+-----+---------+-------+ 
| entity_id | int(64) unsigned | NO | PRI | NULL |  | 
| ip_1  | tinyint(3) unsigned | NO |  | NULL |  | 
| ip_2  | tinyint(3) unsigned | NO |  | NULL |  | 
| ip_3  | tinyint(3) unsigned | NO |  | NULL |  | 
| ip_4  | tinyint(3) unsigned | NO |  | NULL |  | 
| ip_num  | int(64) unsigned | NO |  | 0  |  | 
| ip_poly | geometry   | NO | MUL | NULL |  | 
+------------+---------------------+------+-----+---------+-------+ 

Xin lưu ý rằng tôi không quan tâm đến việc tìm kiếm các hàng cần thiết trong geo_ip bởi chỉ ONE địa chỉ IP cùng một lúc, tôi cần một entity_ip LEFT JOIN geo_ip (hoặc tương tự/cách tương tự).

Đây là những gì tôi có bây giờ (sử dụng đa giác như tư vấn về 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) 

Không đa giác

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) 

(On số cao hơn hàng trong việc tìm kiếm - không có sự khác biệt)

Hiện tại tôi không thể nhận được bất kỳ hiệu suất nào nhanh hơn từ các truy vấn này như 0,1 giây trên mỗi IP là quá chậm đối với tôi.

Có cách nào để làm cho nó nhanh hơn không?

+1

Chụp trong bóng tối: bất kỳ cơ hội nào mà chỉ mục trên 'ip_num' của' entity_ip' sẽ cải thiện tốc độ của truy vấn thứ hai? –

+0

A phải làm điều đó bên trong MySQL?Nếu chúng ta coi ip_num_start và ip_num_end là các điểm liên quan và đọc entity_ip.ip_num theo cách sắp xếp là x-coord của một đường quét trên các chấm, khái niệm về thuật toán đường quét có thể cho bạn chạy nhanh hơn so với n-by-m left tham gia bên trong MySQL. –

+0

Không biết về trường hợp của tác giả, đối với tôi (và nhiều người) nó sẽ rất thú vị để xem giải pháp mysql duy nhất. – Oroboros102

Trả lời

6

Phương pháp này có một số vấn đề về khả năng mở rộng (nếu bạn chọn di chuyển đến dữ liệu địa lý cụ thể của thành phố), nhưng với kích thước dữ liệu nhất định, nó sẽ cung cấp tối ưu hóa đáng kể.

Sự cố bạn đang gặp phải là hiệu quả mà MySQL không tối ưu hóa các truy vấn dựa trên phạm vi rất tốt. Lý tưởng nhất là bạn muốn thực hiện tìm kiếm chính xác ("=") trên chỉ mục thay vì "lớn hơn", vì vậy, chúng tôi sẽ cần tạo chỉ mục giống như từ dữ liệu bạn có sẵn. Bằng cách này, MySQL sẽ có ít hàng hơn để đánh giá trong khi tìm kiếm một kết quả phù hợp.

Để làm điều này, tôi khuyên bạn nên tạo bảng tra cứu lập chỉ mục bảng vị trí địa lý dựa trên octet đầu tiên (= 1 từ 1.2.3.4) của địa chỉ IP. Ý tưởng là đối với mỗi lần tra cứu bạn phải làm, bạn có thể bỏ qua tất cả các IP vị trí địa lý không bắt đầu bằng cùng một octet so với IP bạn đang tìm kiếm.

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; 

Tiếp theo, chúng ta cần phải thực hiện các dữ liệu có sẵn trong bảng định vị của bạn và tạo ra dữ liệu bao gồm tất cả (đầu tiên) octet hàng định vị bao gồm: Nếu bạn có một entry với ip_start = '5.3.0.0'ip_end = '8.16.0.0', các bảng tra cứu sẽ cần hàng cho octet 5, 6, 7 và 8. vì vậy, ...

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

nên chuyển đổi sang:

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

Kể từ ai đó ở đây được yêu cầu cho một giải pháp MySQL bản xứ, đây là một thủ tục lưu trữ mà sẽ tạo ra dữ liệu đó cho bạn:

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; 

Và sau đó bạn sẽ cần phải cư bàn bằng cách gọi rằng thủ tục lưu trữ:

CALL recalculate_ip_geolocation_lookup(); 

Tại thời điểm này, bạn có thể xóa quy trình bạn vừa tạo - nó không còn cần thiết, trừ khi bạn muốn tính toán lại bảng tra cứu.

Sau khi bảng tra cứu được đặt ra, tất cả những gì bạn phải làm là tích hợp nó vào truy vấn của bạn và đảm bảo bạn đang truy vấn bằng octet đầu tiên.truy vấn của bạn đến nhìn lên bảng sẽ làm hài lòng hai điều kiện:

  1. Tìm tất cả các hàng mà phù hợp với octet đầu tiên của địa chỉ IP của bạn
  2. Trong tập hợp con đó: Tìm hàng trong đó có phạm vi phù hợp địa chỉ IP của bạn

Do bước hai được thực hiện trên một tập hợp con dữ liệu, nó nhanh hơn đáng kể so với thực hiện kiểm tra phạm vi trên toàn bộ dữ liệu. Đây là chìa khóa cho chiến lược tối ưu hóa này.

Có nhiều cách khác nhau để tìm ra octet đầu tiên của địa chỉ IP là gì; Tôi đã từng (r.ip_numeric & 0xFF000000) >> 24 từ IP nguồn của tôi là ở dạng số:

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; 

Bây giờ, phải thừa nhận là tôi đã làm được một chút lười biếng cuối cùng: Bạn có thể dễ dàng thoát khỏi ip_geolocation bảng hoàn toàn nếu bạn đã thực hiện bảng ip_geolocation_lookup cũng chứa dữ liệu quốc gia. Tôi đoán việc bỏ một bảng từ truy vấn này sẽ làm cho nó nhanh hơn một chút.

Và cuối cùng, đây là hai bảng khác tôi đã sử dụng trong phản hồi này để tham khảo, vì chúng khác với các bảng của bạn. Tôi chắc chắn họ là tương thích, mặc dù.

# 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

Câu trả lời cực kỳ chi tiết. Xin vui lòng, cho tôi một vài ngày để kiểm tra phương pháp này. Hình như giải pháp làm việc. – Oroboros102

+0

Truy vấn này nhanh hơn nhiều so với fullscan nhưng vẫn cần quét nhiều hàng (range_qty/255). Nếu chúng tôi sẽ sử dụng địa lý ip cho mỗi bảng phạm vi thành phố (30 000 000 hàng) truy vấn này sẽ chậm. Tôi tìm thấy một số soluton, sử dụng hình học. Nếu một trong những câu hỏi của tôi sẽ nhận được bất kỳ câu trả lời thích hợp (http://stackoverflow.com/questions/8244535/joins-on-spatial-mysql-indexes), tôi sẽ có giải pháp tốt hơn cho câu hỏi này. Nếu không - câu trả lời của bạn sẽ là câu trả lời hay nhất. – Oroboros102

+0

Câu hỏi thực sự khác. INNER JOIN hoạt động tốt, trong khi LEFT JOIN sẽ mất ít nhất 4 phút trên bảng 2k entity_ip. –

0

Chỉ muốn trả lại cho cộng đồng:

Dưới đây là một tòa nhà cách thậm chí tốt hơn và tối ưu hóa trên giải pháp Aleksi của:

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(); 

Nó xây dựng cách nhanh hơn so với giải pháp của mình và khoan xuống hơn dễ dàng bởi vì chúng tôi không chỉ lấy 8 đầu tiên, mà là 20 bit đầu tiên. Tham gia hiệu suất: 100000 hàng trong 158ms. Bạn có thể phải đổi tên bảng và tên trường thành phiên bản của bạn.

Query bằng cách sử dụng

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

không thể bình luận nào, nhưng câu trả lời user1281376 là sai và không hoạt động. lý do bạn chỉ sử dụng octet đầu tiên là bởi vì bạn sẽ không phù hợp với tất cả các dãy ip khác. có rất nhiều phạm vi trải rộng nhiều octet thứ hai mà user1281376s thay đổi truy vấn sẽ không khớp. Và có, điều này thực sự xảy ra nếu bạn sử dụng dữ liệu MaxIind GeoIp.

với đề xuất aleksis bạn có thể thực hiện so sánh đơn giản trên octet đầu tiên, do đó giảm tập phù hợp.

+0

Có lẽ tôi nên kiểm tra điều đó nhưng tại thời điểm đó, tôi đã quyết định bỏ qua nó vì nó vẫn hoạt động (tôi nhớ mình cũng đã giả định tác giả đã làm bài tập về nhà của mình). Cảm ơn –

+0

đúng, rõ ràng là nhanh hơn, mặc dù đặc biệt với bảng geoip của maxmind bạn sẽ không khớp với cấp 3 chẳng hạn. đã cho tôi một thời gian để tìm ra lần đầu tiên tôi chạy vào này. Vì vậy, bạn sẽ phải thêm một hàng khác cho end_range và sau đó bạn vẫn bị mắc kẹt với một truy vấn phạm vi. Và thậm chí tệ hơn khi bạn không có một trận đấu cho ip, nó sẽ quét toàn bộ bảng. – knrdk

0

Tôi đã tìm thấy một cách dễ dàng. Tôi nhận thấy rằng tất cả các ip đầu tiên trong nhóm% 256 = 0, vì vậy chúng tôi có thể thêm một bảng 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 

Làm thế nào để điền vào bảng chỉ số

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

Làm thế nào để sử dụng:

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; 

Nhanh hơn 1000 lần.

+0

Vui lòng xem câu trả lời ở trên. –

Các vấn đề liên quan