Este enfoque tiene algunos problemas de escalabilidad (en caso de que decida moverse, por ejemplo, a datos geoip específicos de la ciudad), pero para el tamaño de datos dado, proporcionará una optimización considerable.
El problema al que se enfrenta es que MySQL no optimiza muy bien las consultas basadas en rangos. Idealmente, desea realizar una búsqueda exacta ("=") en un índice en lugar de "mayor que", por lo que necesitaremos crear un índice como ese a partir de los datos que tiene disponibles. De esta forma, MySQL tendrá muchas menos filas para evaluar mientras busca una coincidencia.
Para ello, le sugiero que cree una tabla de búsqueda que indexe la tabla de geolocalización en función del primer octeto (=1 de 1.2.3.4) de las direcciones IP. La idea es que por cada búsqueda que tengas que hacer, puedas ignorar todas las IPs de geolocalización que no comiencen con el mismo octeto que la IP que estás buscando.
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;
A continuación, debemos tomar los datos disponibles en su tabla de geolocalización y generar datos que cubran todos. (primero) octetos que cubre la fila de geolocalización:si tiene una entrada con ip_start = '5.3.0.0'
y ip_end = '8.16.0.0'
, la tabla de búsqueda necesitará filas para los octetos 5, 6, 7 y 8. Entonces...
ip_geolocation
|ip_start |ip_end |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255 |1224701944 |1241743359 |
Debería convertir a:
ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72 |1224701944 |1241743359 |
|73 |1224701944 |1241743359 |
|74 |1224701944 |1241743359 |
Dado que alguien aquí solicitó una solución MySQL nativa, aquí hay un procedimiento almacenado que generará esos datos para usted:
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;
Y luego deberá completar la tabla llamando a ese procedimiento almacenado:
CALL recalculate_ip_geolocation_lookup();
En este punto, puede eliminar el procedimiento que acaba de crear; ya no es necesario, a menos que desee volver a calcular la tabla de búsqueda.
Una vez que la tabla de búsqueda está en su lugar, todo lo que tiene que hacer es integrarla en sus consultas y asegurarse de consultar por el primer octeto. Su consulta a la tabla de búsqueda cumplirá dos condiciones:
- Encuentre todas las filas que coincidan con el primer octeto de su dirección IP
- De ese subconjunto :busque la fila que tiene el rango que coincide con su dirección IP
Debido a que el paso dos se lleva a cabo en un subconjunto de datos, es considerablemente más rápido que hacer las pruebas de rango en todos los datos. Esta es la clave de esta estrategia de optimización.
Hay varias formas de averiguar cuál es el primer octeto de una dirección IP; Usé ( r.ip_numeric & 0xFF000000 ) >> 24
ya que mis direcciones IP de origen están en formato numérico:
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;
Ahora, admito que al final me volví un poco perezoso:podrías deshacerte fácilmente de ip_geolocation
tabla por completo si hizo el ip_geolocation_lookup
La tabla también contiene los datos del país. Supongo que eliminar una tabla de esta consulta lo haría un poco más rápido.
Y, finalmente, aquí están las otras dos tablas que usé en esta respuesta como referencia, ya que difieren de sus tablas. Sin embargo, estoy seguro de que son compatibles.
# 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;