Cette approche présente des problèmes d'évolutivité (si vous choisissez de passer, par exemple, à des données géoip spécifiques à une ville), mais pour la taille donnée des données, elle fournira une optimisation considérable.
Le problème auquel vous êtes confronté est effectivement que MySQL n'optimise pas très bien les requêtes basées sur la plage. Idéalement, vous souhaitez effectuer une recherche exacte ("=") sur un index plutôt que "supérieur à", nous devrons donc créer un index comme celui-ci à partir des données dont vous disposez. De cette façon, MySQL aura beaucoup moins de lignes à évaluer lors de la recherche d'une correspondance.
Pour cela, je vous propose de créer une table de correspondance qui indexe la table de géolocalisation sur le premier octet (=1 de 1.2.3.4) des adresses IP. L'idée est que pour chaque recherche que vous avez à faire, vous pouvez ignorer toutes les IP de géolocalisation qui ne commencent pas par le même octet que l'IP que vous recherchez.
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;
Ensuite, nous devons prendre les données disponibles dans votre table de géolocalisation et produire des données qui couvrent toutes (premiers) octets couverts par la ligne de géolocalisation :si vous avez une entrée avec ip_start = '5.3.0.0'
et ip_end = '8.16.0.0'
, la table de recherche aura besoin de lignes pour les octets 5, 6, 7 et 8. Donc...
ip_geolocation
|ip_start |ip_end |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255 |1224701944 |1241743359 |
Devrait être converti en :
ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72 |1224701944 |1241743359 |
|73 |1224701944 |1241743359 |
|74 |1224701944 |1241743359 |
Étant donné que quelqu'un ici a demandé une solution MySQL native, voici une procédure stockée qui générera ces données pour vous :
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;
Et ensuite, vous devrez remplir la table en appelant cette procédure stockée :
CALL recalculate_ip_geolocation_lookup();
À ce stade, vous pouvez supprimer la procédure que vous venez de créer - elle n'est plus nécessaire, sauf si vous souhaitez recalculer la table de consultation.
Une fois la table de recherche en place, tout ce que vous avez à faire est de l'intégrer dans vos requêtes et de vous assurer que vous interrogez par le premier octet. Votre requête à la table de recherche satisfera à deux conditions :
- Recherchez toutes les lignes qui correspondent au premier octet de votre adresse IP
- De ce sous-ensemble :Trouvez la ligne qui a la plage qui correspond à votre adresse IP
Étant donné que la deuxième étape est effectuée sur un sous-ensemble de données, elle est considérablement plus rapide que d'effectuer les tests de plage sur l'ensemble des données. C'est la clé de cette stratégie d'optimisation.
Il existe plusieurs façons de déterminer quel est le premier octet d'une adresse IP ; J'ai utilisé ( r.ip_numeric & 0xFF000000 ) >> 24
puisque mes IP sources sont sous forme numérique :
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;
Maintenant, certes, je suis devenu un peu paresseux à la fin :vous pouvez facilement vous débarrasser de ip_geolocation
table au total si vous avez fait le ip_geolocation_lookup
Le tableau contient également les données du pays. Je suppose que supprimer une table de cette requête la rendrait un peu plus rapide.
Et, enfin, voici les deux autres tableaux que j'ai utilisés dans cette réponse à titre de référence, car ils diffèrent de vos tableaux. Je suis certain qu'ils sont compatibles, cependant.
# 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;