Този подход има някои проблеми с мащабируемостта (ако решите да преминете, да речем, към специфични за града геоип данни), но за дадения размер на данните той ще осигури значителна оптимизация.
Проблемът, с който се сблъсквате, всъщност е, че MySQL не оптимизира много добре базираните на диапазона заявки. В идеалния случай искате да направите точно ("=") търсене на индекс, а не "по-голямо от", така че ще трябва да изградим индекс като този от наличните данни. По този начин MySQL ще има много по-малко редове за оценка, докато търси съвпадение.
За да направите това, предлагам да създадете таблица за търсене, която индексира таблицата за геолокация въз основа на първия октет (=1 от 1.2.3.4) от IP адресите. Идеята е, че за всяко търсене, което трябва да направите, можете да игнорирате всички IP адреси за геолокация, които не започват със същия октет от IP, който търсите.
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();
В този момент можете да изтриете процедурата, която току-що създадохте – тя вече не е необходима, освен ако не искате да преизчислите таблицата за справка.
След като таблицата за търсене е на мястото си, всичко, което трябва да направите, е да я интегрирате във вашите заявки и да се уверите, че правите заявка до първия октет. Вашата заявка към таблицата за търсене ще удовлетвори две условия:
- Намерете всички редове, които съответстват на първия октет от вашия IP адрес
- От това подмножество :Намерете реда, който съдържа диапазона, който съответства на вашия IP адрес
Тъй като стъпка 2 се извършва върху подмножество от данни, тя е значително по-бърза от извършването на тестове на обхвата на всички данни. Това е ключът към тази стратегия за оптимизиране.
Има различни начини да разберете какъв е първият октет на IP адрес; Използвах ( r.ip_numeric & 0xFF000000 ) >> 24
тъй като моите изходни IP адреси са в числова форма:
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;