2016-04-15 12 views
0

Ich habe eine Tabelle mit 15 Millionen Datensätze mit Namen, E-Mail-Adressen und IPs. Ich muss eine andere Spalte in der gleichen Tabelle mit dem Ländercode mit der IP-Adresse aktualisieren. Ich habe eine kleine Datenbank heruntergeladen (ip2location lite - https://lite.ip2location.com/), die alle IP-Bereiche und assoziierte Länder enthält. Die Tabelle ip2location hat folgende Struktur;So optimieren Sie diese Bereich Abfrage

CREATE TABLE `ip2location_db1` (
    `ip_from` int(10) unsigned DEFAULT NULL, 
    `ip_to` int(10) unsigned DEFAULT NULL, 
    `country_code` char(2) COLLATE utf8_bin DEFAULT NULL, 
    `country_name` varchar(64) COLLATE utf8_bin DEFAULT NULL, 
KEY `idx_ip_from` (`ip_from`), 
KEY `idx_ip_to` (`ip_to`), 
KEY `idx_ip_from_to` (`ip_from`,`ip_to`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin 

Ich verwende die folgende Funktion, um den Ländercode von einer IP-Adresse abzurufen;

CREATE DEFINER=`root`@`localhost` FUNCTION `get_country_code`(
    ipAddress varchar(30) 
) RETURNS VARCHAR(2) 
    DETERMINISTIC 
    BEGIN 
     DECLARE ipNumber INT UNSIGNED; 
     DECLARE countryCode varchar(2); 
     SET ipNumber = SUBSTRING_INDEX(ipAddress, '.', 1) * 16777216; 
     SET ipNumber = ipNumber + (SUBSTRING_INDEX(SUBSTRING_INDEX(ipAddress, '.', 2),'.',-1) * 65536); 
     SET ipNumber = ipNumber + (SUBSTRING_INDEX(SUBSTRING_INDEX(ipAddress, '.', -2),'.',1) * 256); 
     SET ipNumber = ipNumber + SUBSTRING_INDEX(ipAddress, '.', -1); 

     SET countryCode = 
      (SELECT  country_code 
      FROM  ip2location.ip2location_db1 
      USE INDEX (idx_ip_from_to) 
      WHERE  ipNumber >= ip2location.ip2location_db1.ip_from AND ipNumber <= ip2location.ip2location_db1.ip_to 
      LIMIT  1); 

     RETURN countryCode; 
    END$$ 
DELIMITER ; 

Ich habe eine EXPLAIN-Anweisung ausgeführt und dies ist die Ausgabe;

'1', 'SIMPLE', 'ip2location_db1', NULL, 'range', 'idx_ip_from_to', 'idx_ip_from_to', '5', NULL, '1', '33.33', 'Using index condition' 

Mein Problem ist, dass die Abfrage auf 1000 Datensätze ~ 15s nimmt auszuführen, welche die gleiche Abfrage auf alle die Datenbank bedeuten laufen würde mehr als 2 Tage benötigen, um abzuschließen. Gibt es eine Möglichkeit, diese Abfrage zu verbessern?

PS - Wenn ich den USE INDEX (idx_ip_from_to) entfernen, dauert die Abfrage doppelt so lange. Kannst du erklären warum?

Auch ich bin kein Datenbankexperte so mit mir tragen :)

+0

Hat die Tabelle überlappende Bereiche? Wenn ja, können Sie es nicht optimieren (sogar mit Gordons Vorschlag). –

+0

Verwenden Sie nicht 'utf8' für' country_code' - es dauert 6 Bytes, wenn Sie nur 2 benötigen; benutze 'ascii'. –

+0

Was ist mit IPv6? –

Antwort

0

Dies kann sehr schwierig sein. Ich denke, das Problem ist, dass nur der ip_from Teil der Bedingung verwendet werden kann. Sehen Sie, wenn dies die Leistung bekommt man will:

SET countryCode = 
     (SELECT  country_code 
     FROM  ip2location.ip2location_db1 l 
     WHERE  ipNumber >= l.ip_from 
     ORDER BY ip_to 
     LIMIT  1 
     ); 

Ich weiß, ich verlasse die ip_to ab. Wenn dies funktioniert, können Sie den vollständigen Check in zwei Teilen durchführen. Erhalten Sie zuerst die ip_from mit einer ähnlichen Abfrage. Verwenden Sie dann eine Gleichheitsabfrage, um den Rest der Information in der Zeile abzurufen.

+0

Danke ... werde ich jetzt versuchen – claytonc

0

Der Grund USE INDEX hilft, weil MySQL diesen Index nicht verwenden wollte. Sein Optimierer hat einen anderen gewählt, aber er hat falsch geraten. Manchmal passiert das.

Auch ich bin mir nicht sicher, ob dies die Leistung eine Tonne beeinflussen wird, aber Sie sollten einfach INET_ATON verwenden, um die IP-Adresse Zeichenfolge in eine ganze Zahl zu ändern. Sie brauchen das SUBSTRING_INDEX Geschäft nicht, und es kann langsamer sein.

Was würde ich hier tun, ist die maximale Distanz misst zwischen von und nach:

SELECT MAX(ip_from - ip_to) AS distance 
FROM ip2location_db1; 

Angenommen, dies nicht eine dumme Zahl ist, werden Sie dann in der Lage sein, um richtig den ip_from Index zu verwenden. Die Prüfung wird:

WHERE ipNumber BETWEEN ip_from AND ip_from + distance 
    AND ipNumber <= ip_to 

Ziel ist es, alle Informationen, um einen engen Satz zu finden, die Zeilen aus einem begrenzten Bereich von einer Spalte Wert kommen: ip_from. Dann ist ip_to nur eine Genauigkeitsprüfung.

Der Grund dafür ist, dass der ip_to-Wert (zweiter Teil des Index) erst verwendet werden kann, wenn der entsprechende ip_from-Wert gefunden wurde. Daher muss es immer noch die meisten Indexdatensätze für niedrige Werte von ip_from ohne obere Grenze scannen.


Andernfalls könnten Sie in Betracht ziehen zu messen, wie einzigartig die IP-Adressen in Ihren 15 Millionen Datensätzen sind.Wenn beispielsweise nur 5 Millionen eindeutige IPs vorhanden sind, könnte es besser sein, eine eindeutige Liste zu extrahieren, diese den Ländercodes zuzuordnen und diese Zuordnung dann zu verwenden (entweder zur Laufzeit oder zum Aktualisieren der ursprünglichen Tabelle).

Wenn die Werte sehr eindeutig sind, aber möglicherweise in lokalisierten Clustern, können Sie versuchen, die irrelevanten Zeilen aus ip2location_db1 zu entfernen, oder sogar die horizontale Partitionierung, um die Bereichsprüfungen zu verbessern. Ich bin mir nicht sicher, ob das irgendwas gewinnen würde, aber wenn Sie einen Index für die Originaltabelle verwenden können, um nur bestimmte Partitionen zu konsultieren, können Sie vielleicht etwas Leistung gewinnen.

Verwandte Themen