Denne tilgang har nogle skalerbarhedsproblemer (hvis du vælger at flytte til f.eks. byspecifikke geoip-data), men for den givne størrelse af data vil det give betydelig optimering.
Det problem, du står over for, er faktisk, at MySQL ikke optimerer rækkevidde-baserede forespørgsler særlig godt. Ideelt set vil du lave et nøjagtigt ("=") opslag på et indeks i stedet for "større end", så vi bliver nødt til at bygge et indeks som det ud fra de data, du har til rådighed. På denne måde vil MySQL have meget færre rækker at evaluere, mens de leder efter et match.
For at gøre dette foreslår jeg, at du opretter en opslagstabel, der indekserer geolokationstabellen baseret på den første oktet (=1 fra 1.2.3.4) af IP-adresserne. Ideen er, at for hvert opslag, du skal foretage, kan du ignorere alle geolokations-IP'er, som ikke begynder med den samme oktet end den IP, du leder efter.
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;
Dernæst skal vi tage de tilgængelige data i din geolokationstabel og producere data, der dækker alle (første) oktetter, som geolokationsrækken dækker:Hvis du har en post med ip_start = '5.3.0.0'
og ip_end = '8.16.0.0'
, skal opslagstabellen bruge rækker til oktetter 5, 6, 7 og 8. Så...
ip_geolocation
|ip_start |ip_end |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255 |1224701944 |1241743359 |
Bør konvertere til:
ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72 |1224701944 |1241743359 |
|73 |1224701944 |1241743359 |
|74 |1224701944 |1241743359 |
Da nogen her anmodede om en indbygget MySQL-løsning, er her en lagret procedure, der genererer disse data for dig:
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;
Og så bliver du nødt til at udfylde tabellen ved at kalde den lagrede procedure:
CALL recalculate_ip_geolocation_lookup();
På dette tidspunkt kan du slette den procedure, du lige har oprettet -- den er ikke længere nødvendig, medmindre du vil genberegne opslagstabellen.
Når opslagstabellen er på plads, er det eneste, du skal gøre, at integrere den i dine forespørgsler og sørge for, at du forespørger med den første oktet. Din forespørgsel til opslagstabellen vil opfylde to betingelser:
- Find alle rækker, der matcher den første oktet af din IP-adresse
- Af denne delmængde :Find den række, der har det område, der matcher din IP-adresse
Fordi trin to udføres på en delmængde af data, er det betydeligt hurtigere end at udføre rækkeviddetestene på hele dataene. Dette er nøglen til denne optimeringsstrategi.
Der er forskellige måder at finde ud af, hvad den første oktet af en IP-adresse er; Jeg brugte ( r.ip_numeric & 0xFF000000 ) >> 24
da mine kilde-IP'er er i numerisk form:
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;
Nu blev jeg ganske vist lidt doven til sidst:Du kunne nemt slippe af med ip_geolocation
tabellen helt, hvis du lavede ip_geolocation_lookup
tabel indeholder også landedata. Jeg gætter på, at det ville gøre det en smule hurtigere, hvis du dropper én tabel fra denne forespørgsel.
Og endelig, her er de to andre tabeller, jeg brugte i dette svar til reference, da de adskiller sig fra dine tabeller. Jeg er dog sikker på, at de er kompatible.
# 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;