Erstellen einer MySQL-Tabelle mit 1.000 Millionen Zeilen

18

Diese Frage wird aufgrund eines Vorschlags in den Kommentaren von Stack Overflow erneut gestellt. Wir entschuldigen uns für die Vervielfältigung.

Fragen

Frage 1: Wie kann ich MySQL optimieren, um die Geschwindigkeit des LOAD DATA INFILE-Aufrufs zu erhöhen, wenn die Größe der Datenbanktabelle zunimmt?

Frage 2: Würde die Verwendung eines Computerclusters zum Laden verschiedener CSV-Dateien, zur Verbesserung der Leistung oder zum Beenden der Leistung führen? (Dies ist meine Benchmarking-Aufgabe für morgen unter Verwendung der Ladedaten und Masseneinsätze.)

Tor

Wir probieren verschiedene Kombinationen von Feature-Detektoren und Clustering-Parametern für die Bildsuche aus. Daher müssen wir in der Lage sein, zeitnah große Datenbanken zu erstellen.

Maschineninfo

Die Maschine hat 256 GB RAM und es sind weitere 2 Maschinen mit der gleichen RAM-Menge verfügbar, wenn es eine Möglichkeit gibt, die Erstellungszeit durch Verteilen der Datenbank zu verbessern.

Tabellenschema

Das Tabellenschema sieht so aus

+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| match_index   | int(10) unsigned | NO   | PRI | NULL    |                |
| cluster_index | int(10) unsigned | NO   | PRI | NULL    |                |
| id            | int(11)          | NO   | PRI | NULL    | auto_increment |
| tfidf         | float            | NO   |     | 0       |                |
+---------------+------------------+------+-----+---------+----------------+

hergestellt mit

CREATE TABLE test 
(
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL AUTO_INCREMENT,
  tfidf FLOAT NOT NULL DEFAULT 0,
  UNIQUE KEY (id),
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

Bisheriges Benchmarking

Der erste Schritt bestand darin, Masseneinfügungen mit dem Laden aus einer Binärdatei in eine leere Tabelle zu vergleichen.

It took:  0:09:12.394571  to do  4,000  inserts with 5,000 rows per insert
It took: 0:03:11.368320 seconds to load 20,000,000 rows from a csv file

In Anbetracht des Leistungsunterschieds, den ich beim Laden der Daten aus einer binären CSV-Datei festgestellt habe, habe ich zuerst Binärdateien mit 100 KByte, 1 MByte, 20 MByte und 200 MByte Zeilen mit dem folgenden Aufruf geladen.

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test;

Ich habe das Laden der 200M-Zeilen-Binärdatei (~ 3 GB CSV-Datei) nach 2 Stunden abgebrochen.

Also habe ich ein Skript ausgeführt, um die Tabelle zu erstellen und eine andere Anzahl von Zeilen aus einer Binärdatei einzufügen. Dann habe ich die Tabelle gelöscht (siehe Grafik unten).

Bildbeschreibung hier eingeben

Es dauerte ungefähr 7 Sekunden, um 1 Million Zeilen aus der Binärdatei einzufügen. Als Nächstes habe ich mich entschieden, das Einfügen von jeweils 1 Million Zeilen zu vergleichen, um festzustellen, ob es bei einer bestimmten Datenbankgröße zu einem Engpass kommen würde. Sobald die Datenbank ungefähr 59 Millionen Zeilen erreicht hat, ist die durchschnittliche Einfügezeit auf ungefähr 5.000 / Sekunde gesunken

Bildbeschreibung hier eingeben

Durch Festlegen der globalen key_buffer_size = 4294967296 wurden die Geschwindigkeiten für das Einfügen kleinerer Binärdateien geringfügig verbessert. Die folgende Grafik zeigt die Geschwindigkeiten für die unterschiedliche Anzahl von Zeilen

Bildbeschreibung hier eingeben

Beim Einfügen von 1M-Zeilen wurde die Leistung jedoch nicht verbessert.

Zeilen: 1.000.000 Zeit: 0: 04: 13.761428 Einfügungen / Sek .: 3.940

vs für eine leere Datenbank

Zeilen: 1.000.000 Zeit: 0: 00: 6.339295 Einfügungen / Sek .: 315.492

Aktualisieren

Führen Sie die Ladedaten in der folgenden Reihenfolge aus, anstatt nur den Befehl load data zu verwenden

SET autocommit=0;
SET foreign_key_checks=0;
SET unique_checks=0;
LOAD DATA INFILE '/mnt/imagesearch/tests/eggs.csv' INTO TABLE test_ClusterMatches;
SET foreign_key_checks=1;
SET unique_checks=1;
COMMIT;
Bildbeschreibung hier eingeben

In Bezug auf die generierte Datenbankgröße sieht dies also recht vielversprechend aus, aber die anderen Einstellungen scheinen die Leistung des Ladedaten-Infile-Aufrufs nicht zu beeinträchtigen.

Ich habe dann versucht, mehrere Dateien von verschiedenen Computern zu laden, aber der Befehl load data infile sperrt die Tabelle, da bei den anderen Computern aufgrund der Größe der Dateien eine Zeitüberschreitung auftritt

ERROR 1205 (HY000) at line 1: Lock wait timeout exceeded; try restarting transaction

Erhöhen der Anzahl der Zeilen in einer Binärdatei

rows:  10,000,000  seconds rows:  0:01:36.545094  inserts/sec:  103578.541236
rows:  20,000,000  seconds rows:  0:03:14.230782  inserts/sec:  102970.29026
rows:  30,000,000  seconds rows:  0:05:07.792266  inserts/sec:  97468.3359978
rows:  40,000,000  seconds rows:  0:06:53.465898  inserts/sec:  96743.1659866
rows:  50,000,000  seconds rows:  0:08:48.721011  inserts/sec:  94567.8324859
rows:  60,000,000  seconds rows:  0:10:32.888930  inserts/sec:  94803.3646283

Lösung: Berechnen Sie die ID außerhalb von MySQL vor, anstatt die automatische Inkrementierung zu verwenden

Tisch bauen mit

CREATE TABLE test (
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL ,
  tfidf FLOAT NOT NULL DEFAULT 0,
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

mit dem SQL

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';"

Bildbeschreibung hier eingeben

Wenn das Skript veranlasst wird, die Indizes vorab zu berechnen, wurde anscheinend der Leistungstreffer entfernt, wenn die Datenbank größer wird.

Update 2 - mit Speichertabellen

Etwa dreimal schneller, ohne die Kosten für das Verschieben einer In-Memory-Tabelle auf eine festplattenbasierte Tabelle zu berücksichtigen.

rows:  0  seconds rows:  0:00:26.661321  inserts/sec:  375075.18851
rows:  10000000  time:  0:00:32.765095  inserts/sec:  305202.83857
rows:  20000000  time:  0:00:38.937946  inserts/sec:  256818.888187
rows:  30000000  time:  0:00:35.170084  inserts/sec:  284332.559456
rows:  40000000  time:  0:00:33.371274  inserts/sec:  299658.922222
rows:  50000000  time:  0:00:39.396904  inserts/sec:  253827.051994
rows:  60000000  time:  0:00:37.719409  inserts/sec:  265115.500617
rows:  70000000  time:  0:00:32.993904  inserts/sec:  303086.291334
rows:  80000000  time:  0:00:33.818471  inserts/sec:  295696.396209
rows:  90000000  time:  0:00:33.534934  inserts/sec:  298196.501594

Durch das Laden der Daten in eine speicherbasierte Tabelle und anschließendes Kopieren in eine festplattenbasierte Tabelle in Blöcken ergab sich ein Overhead von 10 Minuten und 59,71 Sekunden, um 107.356.741 Zeilen mit der Abfrage zu kopieren

insert into test Select * from test2;

Das Laden von 100 Millionen Zeilen dauert ungefähr 15 Minuten. Dies entspricht in etwa dem direkten Einfügen in eine festplattenbasierte Tabelle.

Ben
quelle
1
Ich denke, dass das Ändern des Primärschlüssels zu einfach idschneller sein sollte. (Obwohl ich denke, dass Sie nicht danach suchen)
DavidEG
Hallo David, danke für den Kommentar, leider sind die Abfragen ohne den Schlüssel nicht schnell genug (die Logik hinter der Auswahl des Primärschlüssels ist in diesem Beitrag beschrieben. Stackoverflow.com/questions/4282526/mysql-group-by- Optimierung )
Ben
1
Ist das nur zum Testen? Vielleicht möchten Sie sich die MySQL MEMORY-Engine ansehen : dev.mysql.com/doc/refman/5.0/de/memory-storage-engine.html Wenn Sie vorhaben, diese als Architektur bereitzustellen, bin ich gespannt, wie Sie dies planen Es scheint so, als würde MapReduce / Hadoop die Wiederherstellung nach Fehlern verbessern.
Polynom
Hallo Polynom, danke für den Tipp, im Moment testen wir nur verschiedene Feature-Detektoren in verschiedenen Maßstäben. Sobald die Datenbank erstellt ist, ändert sich nicht viel (in der aktuellen Spezifikation sowieso)
Ben

Antworten:

4

Gute Frage - gut erklärt.

Wie kann ich MySQL optimieren, um die Geschwindigkeit des LOAD DATA INFILE-Aufrufs zu erhöhen?

Sie haben bereits eine hohe (ish) Einstellung für den Schlüsselpuffer - aber ist das genug? Ich gehe davon aus, dass dies eine 64-Bit-Installation ist (wenn nicht, müssen Sie zuerst ein Upgrade durchführen) und nicht auf MSNT ausgeführt werden. Schauen Sie sich nach einigen Tests die Ausgabe von mysqltuner.pl an.

Um den Cache optimal zu nutzen, bietet das Stapeln / Vorsortieren der Eingabedaten möglicherweise Vorteile (die neuesten Versionen des Befehls 'sort' bieten zahlreiche Funktionen zum Sortieren großer Datensätze). Auch wenn Sie die ID-Nummern außerhalb von MySQL generieren, ist dies möglicherweise effizienter.

würde einen Cluster von Computern verwenden, um verschiedene CSV-Dateien zu laden

Angenommen (erneut), Sie möchten, dass die Ausgabemenge sich wie eine einzelne Tabelle verhält, dann haben Sie nur den Vorteil, dass Sie die Arbeit des Sortierens und Generierens von IDs verteilen - für die Sie keine weiteren Datenbanken benötigen. Wenn Sie OTOH mit einem Datenbankcluster verwenden, treten Probleme mit Konflikten auf (die Sie nur als Leistungsprobleme ansehen sollten).

Wenn Sie die Daten aufteilen und die resultierenden Datasets unabhängig voneinander verarbeiten können, erhalten Sie Leistungsvorteile - dies macht jedoch nicht die Notwendigkeit zunichte, jeden Knoten zu optimieren.

Stellen Sie sicher, dass Sie mindestens 4 GB für die sort_buffer_size haben.

Darüber hinaus hängt der Leistungsbeschränkungsfaktor von der Festplatten-E / A ab. Es gibt viele Möglichkeiten, dies zu beheben - aber Sie sollten wahrscheinlich einen gespiegelten Satz gestreifter Datensätze auf SSDs in Betracht ziehen, um eine optimale Leistung zu erzielen.

symcbean
quelle
1
  • Betrachten Sie Ihren begrenzenden Faktor. Es ist fast sicher Single-Threaded-CPU-Verarbeitung.
  • Sie haben bereits festgestellt, dass dies load data...schneller ist als das Einfügen. Verwenden Sie das also.
  • Sie haben bereits festgestellt, dass sehr große Dateien (nach Zeilennummer) die Arbeit erheblich verlangsamen. Sie möchten sie in Stücke zerbrechen.
  • Wenn Sie nicht überlappende Primärschlüssel verwenden, müssen Sie mindestens N * CPU-Sätze in die Warteschlange stellen. Verwenden Sie dabei nicht mehr als eine Million Zeilen ... wahrscheinlich weniger (Benchmark).
  • Verwenden Sie in jeder Datei aufeinanderfolgende Primärschlüsselblöcke.

Wenn Sie wirklich elegant sein möchten, können Sie ein Multithread-Programm erstellen, um eine einzelne Datei einer Sammlung von Named Pipes zuzuführen und die Insert-Instanzen zu verwalten.

Zusammenfassend gesagt, optimieren Sie MySQL nicht so sehr, als dass Sie Ihre Arbeitslast auf MySQL optimieren.

Jeff Ferland
quelle
-1

Ich erinnere mich nicht genau an die Syntacx, aber wenn es Inno-DB ist, können Sie die Fremdschlüsselprüfung deaktivieren.

Sie können den Index auch nach dem Import erstellen. Dies kann ein echter Leistungsgewinn sein.

Julien Duponchelle
quelle
Wenn Sie die Neuerstellung des Index zurückstellen, wird die Leistung nur dann verbessert, wenn die Anzahl der bereits in der Tabelle enthaltenen Zeilen erheblich geringer ist als die Anzahl der von Ihnen hinzugefügten Zeilen.
symcbean