Mysql
 sql >> Database >  >> RDS >> Mysql

La tabella GeoIP si unisce alla tabella degli IP in MySQL

Questo approccio presenta alcuni problemi di scalabilità (dovresti scegliere di passare, ad esempio, a dati geoip specifici per città), ma per la dimensione data dei dati, fornirà una notevole ottimizzazione.

Il problema che stai affrontando è effettivamente che MySQL non ottimizza molto bene le query basate sull'intervallo. Idealmente si desidera eseguire una ricerca esatta ("=") su un indice anziché su "maggiore di", quindi dovremo creare un indice del genere dai dati che hai a disposizione. In questo modo MySQL avrà molte meno righe da valutare durante la ricerca di una corrispondenza.

Per fare ciò, ti suggerisco di creare una tabella di ricerca che indicizzi la tabella di geolocalizzazione in base al primo ottetto (=1 da 1.2.3.4) degli indirizzi IP. L'idea è che per ogni ricerca che devi fare, puoi ignorare tutti gli IP di geolocalizzazione che non iniziano con lo stesso ottetto dell'IP che stai cercando.

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;

Successivamente, dobbiamo prendere i dati disponibili nella tua tabella di geolocalizzazione e produrre dati che coprano tutto (primo) ottetti che la riga di geolocalizzazione copre:se hai una voce con ip_start = '5.3.0.0' e ip_end = '8.16.0.0' , la tabella di ricerca avrà bisogno di righe per gli ottetti 5, 6, 7 e 8. Quindi...

ip_geolocation
|ip_start       |ip_end          |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255    |1224701944      |1241743359    |

Dovrebbe essere convertito in:

ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72         |1224701944      |1241743359    |
|73         |1224701944      |1241743359    |
|74         |1224701944      |1241743359    |

Poiché qualcuno qui ha richiesto una soluzione MySQL nativa, ecco una procedura memorizzata che genererà quei dati per te:

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;

E poi dovrai popolare la tabella chiamando quella stored procedure:

CALL recalculate_ip_geolocation_lookup();

A questo punto puoi cancellare la procedura che hai appena creato -- non è più necessaria, a meno che tu non voglia ricalcolare la tabella di ricerca.

Dopo che la tabella di ricerca è a posto, tutto ciò che devi fare è integrarla nelle tue query e assicurarti di eseguire query dal primo ottetto. La tua richiesta alla tabella di ricerca soddisferà due condizioni:

  1. Trova tutte le righe che corrispondono al primo ottetto del tuo indirizzo IP
  2. Di quel sottoinsieme :trova la riga che ha l'intervallo che corrisponde al tuo indirizzo IP

Poiché il passaggio due viene eseguito su un sottoinsieme di dati, è notevolmente più veloce rispetto all'esecuzione dei test di intervallo su tutti i dati. Questa è la chiave di questa strategia di ottimizzazione.

Esistono vari modi per capire qual è il primo ottetto di un indirizzo IP; Ho usato ( r.ip_numeric & 0xFF000000 ) >> 24 poiché i miei IP di origine sono in forma numerica:

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;

Ora, devo ammettere che alla fine sono diventato un po' pigro:potresti facilmente sbarazzarti di ip_geolocation tabella del tutto se hai creato ip_geolocation_lookup la tabella contiene anche i dati del paese. Immagino che eliminare una tabella da questa query lo renderebbe un po' più veloce.

E, infine, ecco le altre due tabelle che ho usato in questa risposta come riferimento, poiché differiscono dalle tue tabelle. Sono certo che sono compatibili, però.

# 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;