Takie podejście ma pewne problemy ze skalowalnością (jeśli zdecydujesz się na przejście do, powiedzmy, danych geoip specyficznych dla miasta), ale dla danego rozmiaru danych zapewni znaczną optymalizację.
Problem, z którym się zmagasz, polega na tym, że MySQL nie optymalizuje zbyt dobrze zapytań opartych na zakresach. W idealnym przypadku chcesz przeprowadzić dokładne ("=") wyszukiwanie indeksu, a nie "większy niż", więc musimy zbudować taki indeks na podstawie dostępnych danych. W ten sposób MySQL będzie miał znacznie mniej wierszy do oceny podczas wyszukiwania dopasowania.
W tym celu proponuję utworzyć tabelę przeglądową, która indeksuje tabelę geolokalizacji na podstawie pierwszego oktetu (=1 z 1.2.3.4) adresów IP. Pomysł polega na tym, że dla każdego wyszukiwania, które musisz wykonać, możesz zignorować wszystkie adresy IP geolokalizacji, które nie zaczynają się od tego samego oktetu niż adres IP, którego szukasz.
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;
Następnie musimy zebrać dane dostępne w Twojej tabeli geolokalizacji i wytworzyć dane, które obejmują wszystko (pierwszy) oktet obejmuje wiersz geolokalizacji:Jeśli masz wpis z ip_start = '5.3.0.0'
i ip_end = '8.16.0.0'
, tabela przeglądowa będzie potrzebować wierszy dla oktetów 5, 6, 7 i 8. Więc...
ip_geolocation
|ip_start |ip_end |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255 |1224701944 |1241743359 |
Powinno się przekonwertować na:
ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72 |1224701944 |1241743359 |
|73 |1224701944 |1241743359 |
|74 |1224701944 |1241743359 |
Ponieważ ktoś tutaj poprosił o natywne rozwiązanie MySQL, oto procedura składowana, która wygeneruje te dane za Ciebie:
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;
Następnie musisz wypełnić tabelę, wywołując tę procedurę składowaną:
CALL recalculate_ip_geolocation_lookup();
W tym momencie możesz usunąć właśnie utworzoną procedurę - nie jest już potrzebna, chyba że chcesz ponownie obliczyć tabelę przeglądową.
Po utworzeniu tabeli przeglądowej wszystko, co musisz zrobić, to zintegrować ją z zapytaniami i upewnić się, że wykonujesz zapytania według pierwszego oktetu. Twoje zapytanie do tabeli przeglądowej spełnia dwa warunki:
- Znajdź wszystkie wiersze, które pasują do pierwszego oktetu Twojego adresu IP
- Z tego podzbioru :Znajdź wiersz z zakresem odpowiadającym Twojemu adresowi IP
Ponieważ krok drugi jest wykonywany na podzbiorze danych, jest znacznie szybszy niż testowanie zakresu na całych danych. To jest klucz do tej strategii optymalizacji.
Istnieją różne sposoby ustalenia, jaki jest pierwszy oktet adresu IP; Użyłem ( r.ip_numeric & 0xFF000000 ) >> 24
ponieważ moje źródłowe adresy IP są w postaci liczbowej:
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;
Muszę przyznać, że w końcu trochę się rozleniwiłem:możesz łatwo pozbyć się ip_geolocation
w ogóle, jeśli utworzyłeś ip_geolocation_lookup
tabela zawiera również dane dotyczące kraju. Domyślam się, że usunięcie jednej tabeli z tego zapytania przyspieszyłoby to zadanie.
I na koniec, oto dwie inne tabele, których użyłem w tej odpowiedzi w celach informacyjnych, ponieważ różnią się one od twoich tabel. Jestem jednak pewien, że są kompatybilne.
# 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;