sql >> Database >  >> RDS >> Mysql

GeoIP table join met tabel met IP's in MySQL

Deze aanpak heeft enkele schaalbaarheidsproblemen (als u ervoor kiest om bijvoorbeeld naar stadsspecifieke geoip-gegevens te gaan), maar voor de gegeven grootte van de gegevens zal het aanzienlijke optimalisatie bieden.

Het probleem waarmee u wordt geconfronteerd, is dat MySQL op bereik gebaseerde zoekopdrachten niet zo goed optimaliseert. In het ideale geval wil je een exacte ("=") look-up doen op een index in plaats van "groter dan", dus we zullen een dergelijke index moeten bouwen op basis van de gegevens die je beschikbaar hebt. Op deze manier heeft MySQL veel minder rijen om te evalueren tijdens het zoeken naar een match.

Om dit te doen, stel ik voor dat u een opzoektabel maakt die de geolocatietabel indexeert op basis van het eerste octet (=1 van 1.2.3.4) van de IP-adressen. Het idee is dat je voor elke zoekopdracht die je moet doen alle geolocatie-IP's kunt negeren die niet met hetzelfde octet beginnen als het IP-adres dat je zoekt.

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;

Vervolgens moeten we de beschikbare gegevens in uw geolocatietabel gebruiken en gegevens produceren die alles dekken (eerste) octetten die de geolocatierij beslaat:Als u een invoer heeft met ip_start = '5.3.0.0' en ip_end = '8.16.0.0' , heeft de opzoektabel rijen nodig voor octetten 5, 6, 7 en 8. Dus...

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

Moet converteren naar:

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

Aangezien iemand hier om een ​​native MySQL-oplossing heeft gevraagd, is hier een opgeslagen procedure die die gegevens voor u zal genereren:

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;

En dan moet je de tabel vullen door die opgeslagen procedure aan te roepen:

CALL recalculate_ip_geolocation_lookup();

Op dit punt kunt u de procedure die u zojuist hebt gemaakt verwijderen -- deze is niet langer nodig, tenzij u de opzoektabel opnieuw wilt berekenen.

Nadat de opzoektabel op zijn plaats is, hoeft u deze alleen maar in uw query's te integreren en ervoor te zorgen dat u met het eerste octet zoekt. Uw zoekopdracht naar de opzoektabel voldoet aan twee voorwaarden:

  1. Zoek alle rijen die overeenkomen met het eerste octet van uw IP-adres
  2. Van die subset :Zoek de rij met het bereik dat overeenkomt met uw IP-adres

Omdat stap twee wordt uitgevoerd op een subset van gegevens, is dit aanzienlijk sneller dan het uitvoeren van de bereiktests op de volledige gegevens. Dit is de sleutel tot deze optimalisatiestrategie.

Er zijn verschillende manieren om erachter te komen wat het eerste octet van een IP-adres is; Ik gebruikte ( r.ip_numeric & 0xFF000000 ) >> 24 aangezien mijn bron-IP's in numerieke vorm zijn:

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;

Toegegeven, ik werd uiteindelijk een beetje lui:je zou gemakkelijk van ip_geolocation af kunnen komen tabel als u de ip_geolocation_lookup . hebt gemaakt tabel bevatten ook de landgegevens. Ik vermoed dat het een beetje sneller zou zijn als je één tabel uit deze query laat vallen.

En ten slotte zijn hier de twee andere tabellen die ik in dit antwoord ter referentie heb gebruikt, omdat ze verschillen van uw tabellen. Ik ben er echter zeker van dat ze compatibel zijn.

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


  1. Hoe het mysql root-wachtwoord opnieuw in te stellen?

  2. Trigger om M-M-relatie af te dwingen

  3. MySQL-tijdstempel alleen bij aanmaken

  4. Is er een SQL Server Profiler voor SQL Server Express?