Dieser Ansatz weist einige Skalierbarkeitsprobleme auf (sollten Sie beispielsweise zu stadtspezifischen GeoIP-Daten wechseln), aber für die gegebene Datengröße bietet er eine erhebliche Optimierung.
Das Problem, dem Sie gegenüberstehen, besteht effektiv darin, dass MySQL bereichsbasierte Abfragen nicht sehr gut optimiert. Idealerweise möchten Sie einen Index genau ("=") suchen und nicht "größer als", also müssen wir einen solchen Index aus den verfügbaren Daten erstellen. Auf diese Weise muss MySQL viel weniger Zeilen auswerten, während es nach einer Übereinstimmung sucht.
Dazu schlage ich vor, dass Sie eine Nachschlagetabelle erstellen, die die Geolokalisierungstabelle basierend auf dem ersten Oktett (=1 von 1.2.3.4) der IP-Adressen indiziert. Die Idee ist, dass Sie bei jeder Suche, die Sie durchführen müssen, alle Geolokalisierungs-IPs ignorieren können, die nicht mit demselben Oktett beginnen wie die IP, nach der Sie suchen.
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;
Als Nächstes müssen wir die in Ihrer Geolokalisierungstabelle verfügbaren Daten nehmen und Daten erstellen, die alle abdecken (erstes) Oktett umfasst die Geolocation-Zeile:Wenn Sie einen Eintrag mit ip_start = '5.3.0.0'
haben und ip_end = '8.16.0.0'
, benötigt die Nachschlagetabelle Zeilen für die Oktette 5, 6, 7 und 8. Also ...
ip_geolocation
|ip_start |ip_end |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255 |1224701944 |1241743359 |
Sollte konvertiert werden in:
ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72 |1224701944 |1241743359 |
|73 |1224701944 |1241743359 |
|74 |1224701944 |1241743359 |
Da hier jemand nach einer nativen MySQL-Lösung gefragt hat, ist hier eine gespeicherte Prozedur, die diese Daten für Sie generiert:
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;
Und dann müssen Sie die Tabelle füllen, indem Sie diese gespeicherte Prozedur aufrufen:
CALL recalculate_ip_geolocation_lookup();
An dieser Stelle können Sie die soeben erstellte Prozedur löschen – sie wird nicht mehr benötigt, es sei denn, Sie möchten die Nachschlagetabelle neu berechnen.
Nachdem die Nachschlagetabelle vorhanden ist, müssen Sie sie nur noch in Ihre Abfragen integrieren und sicherstellen, dass Sie nach dem ersten Oktett abfragen. Ihre Abfrage an die Nachschlagetabelle erfüllt zwei Bedingungen:
- Finde alle Zeilen, die mit dem ersten Oktett deiner IP-Adresse übereinstimmen
- Von dieser Teilmenge :Suchen Sie die Zeile mit dem Bereich, der Ihrer IP-Adresse entspricht
Da der zweite Schritt an einer Teilmenge von Daten ausgeführt wird, ist er erheblich schneller als die Durchführung der Reichweitentests an den gesamten Daten. Das ist der Schlüssel zu dieser Optimierungsstrategie.
Es gibt verschiedene Möglichkeiten, herauszufinden, was das erste Oktett einer IP-Adresse ist; Ich habe ( r.ip_numeric & 0xFF000000 ) >> 24
verwendet da meine Quell-IPs in numerischer Form vorliegen:
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;
Nun, zugegebenermaßen bin ich am Ende doch etwas faul geworden:ip_geolocation
könnte man leicht loswerden Tabelle zusammen, wenn Sie ip_geolocation_lookup
gemacht haben Tabelle enthalten auch die Länderdaten. Ich vermute, das Löschen einer Tabelle aus dieser Abfrage würde es etwas schneller machen.
Und schließlich sind hier die beiden anderen Tabellen, die ich in dieser Antwort als Referenz verwendet habe, da sie sich von Ihren Tabellen unterscheiden. Ich bin mir aber sicher, dass sie kompatibel sind.
# 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;