Was ist schneller: mehrere einzelne INSERTs oder ein mehrzeiliges INSERT?

183

Ich versuche, einen Teil meines Codes zu optimieren, der Daten in MySQL einfügt. Sollte ich INSERTs verketten, um ein großes mehrzeiliges INSERT zu erstellen, oder sind mehrere separate INSERTs schneller?

dusoft
quelle

Antworten:

286

https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html

Die zum Einfügen einer Zeile erforderliche Zeit wird durch die folgenden Faktoren bestimmt, wobei die Zahlen ungefähre Proportionen angeben:

  • Anschließen: (3)
  • Senden einer Anfrage an den Server: (2)
  • Analyseabfrage: (2)
  • Zeile einfügen: (1 × Zeilengröße)
  • Einfügen von Indizes: (1 × Anzahl der Indizes)
  • Abschluss: (1)

Daraus sollte ersichtlich sein, dass Sie durch das Senden einer großen Anweisung einen Overhead von 7 pro Einfügeanweisung sparen, was im weiteren Lesen des Textes auch besagt:

Wenn Sie mehrere Zeilen gleichzeitig vom selben Client einfügen, verwenden Sie INSERT-Anweisungen mit mehreren VALUES-Listen, um mehrere Zeilen gleichzeitig einzufügen. Dies ist erheblich schneller (in einigen Fällen um ein Vielfaches schneller) als die Verwendung separater einzeiliger INSERT-Anweisungen.

mbarkhau
quelle
27
Wie trifft diese Antwort zu, wenn sich mehrere einzelne INSERTs innerhalb derselben Datenbanktransaktion befinden?
Prise
2
Wie viele Zeilen kann ich gleichzeitig mit einer einzelnen Einfügeanweisung einfügen? Kann ich 10000 Zeilen gleichzeitig einfügen?
Naresh Ramoliya
10
@Pinch Durch die Verwendung einer Transaktion während ~ 1,5.000 Upsets (Einfügen / Aktualisieren) wurde die Zeit, die der Vorgang dauerte, von ~ 1,5 Sekunden auf ~ 0,2 Sekunden verringert. Mit anderen Worten, es war 86% schneller als einreihige Einsätze. Verdammt.
fgblomqvist
1
Hinweis: Scheint in MSSQL sehr unterschiedlich zu sein: stackoverflow.com/questions/8635818/…
Marsze
Wie wäre es mit Prepared Statement zum Einfügen von sich wiederholenden mehreren Einzeleinfügungen?
Priyabagus
151

Ich weiß, dass ich diese Frage fast zweieinhalb Jahre nach ihrer Beantwortung beantworte, aber ich wollte nur einige harte Daten aus einem Projekt bereitstellen, an dem ich gerade arbeite, was zeigt, dass es VIEL ist, tatsächlich mehrere VALUE-Blöcke pro Einfügung zu erstellen schneller als sequentielle INSERT-Anweisungen eines einzelnen VALUE-Blocks.

Der Code, den ich für diesen Benchmark in C # geschrieben habe, verwendet ODBC, um Daten aus einer MSSQL-Datenquelle (~ 19.000 Zeilen, alle werden vor Beginn des Schreibens gelesen) in den Speicher einzulesen Fügen Sie die Daten aus dem Speicher über vorbereitete Anweisungen in eine Tabelle auf einem MySQL-Server ein. Es wurde so geschrieben, dass ich die Anzahl der VALUE-Blöcke pro vorbereitetem INSERT dynamisch anpassen kann (dh n Zeilen gleichzeitig einfügen, wobei ich den Wert von n vor einem Lauf anpassen kann). Außerdem habe ich den Test ausgeführt mehrmals für jedes n.

Das Ausführen einzelner VALUE-Blöcke (z. B. jeweils 1 Zeile) dauerte 5,7 bis 5,9 Sekunden. Die anderen Werte sind wie folgt:

2 Zeilen gleichzeitig: 3,5 - 3,5 Sekunden
5 Zeilen gleichzeitig: 2,2 - 2,2 Sekunden
10 Zeilen gleichzeitig: 1,7 - 1,7 Sekunden
50 Zeilen gleichzeitig: 1,17 - 1,18 Sekunden
100 Zeilen gleichzeitig: 1,1 - 1,4 Sekunden
500 Zeilen gleichzeitig: 1,1 - 1,2 Sekunden
1000 Zeilen gleichzeitig: 1,17 - 1,17 Sekunden

Ja, schon das Bündeln von 2 oder 3 Schreibvorgängen führt zu einer dramatischen Verbesserung der Geschwindigkeit (Laufzeitverkürzung um den Faktor n), bis Sie irgendwo zwischen n = 5 und n = 10 ankommen. An diesem Punkt fällt die Verbesserung deutlich ab. und irgendwo im Bereich von n = 10 bis n = 50 wird die Verbesserung vernachlässigbar.

Ich hoffe, dies hilft den Leuten bei der Entscheidung, (a) ob die Multiprepare-Idee verwendet werden soll und (b) wie viele VALUE-Blöcke pro Anweisung erstellt werden sollen (vorausgesetzt, Sie möchten mit Daten arbeiten, die groß genug sind, um die Abfrage über die maximale Abfragegröße hinauszuschieben für MySQL, von dem ich glaube, dass es an vielen Stellen standardmäßig 16 MB groß ist, möglicherweise größer oder kleiner, abhängig vom Wert von max_allowed_packet, der auf dem Server festgelegt ist.)

Jon Kloske
quelle
1
Klärungsanfrage: ist Ihre Zeit "Sekunden pro Zeile" oder "Sekunden insgesamt".
EngrStudent
3
Sekunden insgesamt - Sekunden pro Zeile sind also geteilt durch die ~ 19.000 Zeilen. Obwohl dies eine kleine Zahl ist, ist Zeilen / Sekunde möglicherweise eine bessere Metrik, wenn Sie nach einer leicht vergleichbaren Zahl suchen.
Jon Kloske
Übrigens gibt es einen beispielhaften .NET-Code für den Ansatz, den ich oben für meine verwandte Antwort beschrieben habe: stackoverflow.com/questions/25377357/…
Jon Kloske
18

Ein wichtiger Faktor ist, ob Sie eine Transaktions-Engine verwenden und ob Sie eine automatische Festschreibung aktiviert haben.

Autocommit ist standardmäßig aktiviert und Sie möchten es wahrscheinlich aktiviert lassen. Daher führt jede Einfügung, die Sie ausführen, eine eigene Transaktion aus. Dies bedeutet, dass Sie, wenn Sie eine Einfügung pro Zeile ausführen, für jede Zeile eine Transaktion festschreiben.

Unter der Annahme eines einzelnen Threads bedeutet dies, dass der Server einige Daten für JEDE REIHE mit der Disc synchronisieren muss. Es muss warten, bis die Daten einen dauerhaften Speicherort erreicht haben (hoffentlich der batteriegepufferte RAM in Ihrem RAID-Controller). Dies ist von Natur aus ziemlich langsam und wird in diesen Fällen wahrscheinlich zum begrenzenden Faktor.

Ich gehe natürlich davon aus, dass Sie eine Transaktions-Engine verwenden (normalerweise innodb) UND dass Sie die Einstellungen nicht angepasst haben, um die Haltbarkeit zu verringern.

Ich gehe auch davon aus, dass Sie einen einzelnen Thread verwenden, um diese Einfügungen durchzuführen. Die Verwendung mehrerer Threads trübt die Dinge ein wenig, da einige Versionen von MySQL in innodb über ein Arbeitsgruppen-Commit verfügen. Dies bedeutet, dass mehrere Threads, die ihre eigenen Commits ausführen, einen einzigen Schreibvorgang für das Transaktionsprotokoll gemeinsam nutzen können. Dies ist gut, da weniger Synchronisierungen mit dauerhaftem Speicher erforderlich sind .

Auf der anderen Seite ist das Ergebnis, dass Sie WIRKLICH mehrzeilige Einfügungen verwenden möchten.

Es gibt eine Grenze, über die es kontraproduktiv wird, aber in den meisten Fällen sind es mindestens 10.000 Zeilen. Wenn Sie also bis zu 1.000 Zeilen stapeln, sind Sie wahrscheinlich sicher.

Wenn Sie MyISAM verwenden, gibt es eine ganze Reihe anderer Dinge, aber ich werde Sie damit nicht langweilen. Frieden.

MarkR
quelle
1
Gibt es einen Grund, warum es nach einem Punkt kontraproduktiv wird? Ich habe es auch schon einmal gesehen, war mir aber nicht sicher warum.
Dhruv Gairola
1
Wissen Sie , ob es einen Punkt überhaupt in Dosieren MySQL Einsätze bei Transaktionen . Ich frage mich nur, ob ich mir die Mühe ersparen kann, den mehrwertigen SQL-Befehl generieren zu müssen, wenn meine zugrunde liegende Bibliothek (Java JDBC - mysql-connector-java-5.1.30) erst dann festgeschrieben wird, wenn ich es ihm sage.
RTF
@RTF Ich denke, Sie müssen einen kleinen Test durchführen, um dieses Verhalten in Ihrer Situation zu bestimmen, da es sich um ein stark implementierungsspezifisches Verhalten handelt. In vielen Fällen sollten Transaktionen jedoch ähnliche Leistungssteigerungen bieten.
Jasmine Hegman
9

Senden Sie so viele Einsätze wie möglich gleichzeitig über den Draht. Die tatsächliche Einfügungsgeschwindigkeit sollte gleich sein, aber Sie werden Leistungssteigerungen durch die Reduzierung des Netzwerk-Overheads sehen.

RC.
quelle
7

Im Allgemeinen ist die Anzahl der Aufrufe der Datenbank umso besser (dh schneller und effizienter). Versuchen Sie daher, die Einfügungen so zu codieren, dass Datenbankzugriffe minimiert werden. Denken Sie daran, dass jeder Datenbankzugriff, sofern Sie keinen Verbindungspool verwenden, eine Verbindung herstellen, die SQL ausführen und dann die Verbindung abbrechen muss. Ziemlich viel Aufwand!

ennuikiller
quelle
Was ist, wenn die dauerhafte Verbindung verwendet wird?
Dusoft
6
Es ist noch Overhead. Die Transitzeit allein (von und zu für jede einzelne Beilage) ist schnell erkennbar, wenn Sie Tausende von Beilagen ausführen.
RC.
4

Sie möchten vielleicht :

  • Überprüfen Sie, ob die automatische Festschreibung deaktiviert ist
  • Verbindung öffnen
  • Senden Sie mehrere Stapel von Einfügungen in einer einzigen Transaktion (Größe von ca. 4000-10000 Zeilen? Sie sehen)
  • Verbindung schließen

Je nachdem , wie gut Ihre Server Waage (seine endgültig in Ordnung mit PostgreSQl, Oracleund MSSQL), tat das , was oben mit mehreren Threads und mehr Verbindungen.

Antibarbie
quelle
3

Im Allgemeinen sind mehrere Einfügungen aufgrund des Verbindungsaufwands langsamer. Wenn Sie mehrere Einsätze gleichzeitig ausführen, reduzieren sich die Kosten für den Overhead pro Einsatz.

Abhängig davon, welche Sprache Sie verwenden, können Sie möglicherweise einen Stapel in Ihrer Programmier- / Skriptsprache erstellen, bevor Sie zur Datenbank gehen und jede Einfügung zum Stapel hinzufügen. Dann könnten Sie einen großen Stapel mit einem Verbindungsvorgang ausführen. Hier ist ein Beispiel in Java.

Chris Williams
quelle
3

MYSQL 5.5 Eine SQL-Insert-Anweisung dauerte ~ 300 bis ~ 450 ms. Die folgenden Statistiken gelten für Inline-Anweisungen für mehrere Einfügungen.

(25492 row(s) affected)
Execution Time : 00:00:03:343
Transfer Time  : 00:00:00:000
Total Time     : 00:00:03:343

Ich würde sagen, Inline ist der richtige Weg :)

A_01
quelle
0

Es ist lächerlich, wie schlecht MySQL und MariaDB optimiert sind, wenn es um Beilagen geht. Ich habe MySQL 5.7 und Mariadb 10.3 getestet, kein wirklicher Unterschied.

Ich habe dies auf einem Server mit NVME-Festplatten, 70.000 IOPS, 1,1 GB / s Seq-Durchsatz getestet, und das ist möglicher Vollduplex (Lesen und Schreiben).
Der Server ist ebenfalls ein Hochleistungsserver.
Gab es 20 GB RAM.
Die Datenbank ist vollständig leer.

Die Geschwindigkeit, die ich erhalte, betrug 5000 Einfügungen pro Sekunde, wenn mehrzeilige Einfügungen durchgeführt wurden (versucht mit 1 MB bis zu 10 MB Datenblöcken)

Nun der Hinweis:
Wenn ich einen weiteren Thread hinzufüge und in die GLEICHEN Tabellen einfüge, habe ich plötzlich 2x5000 / Sek. Noch ein Thread und ich habe insgesamt 15000 / Sek

Beachten Sie Folgendes: Wenn Sie EINEN Thread einfügen, bedeutet dies, dass Sie nacheinander auf die Festplatte schreiben können (mit Ausnahme von Indizes). Wenn Sie Threads verwenden, verschlechtern Sie tatsächlich die mögliche Leistung, da jetzt viel mehr zufällige Zugriffe erforderlich sind. Die Realitätsprüfung zeigt jedoch, dass MySQL so schlecht optimiert ist, dass Threads sehr hilfreich sind.

Die tatsächliche Leistung, die mit einem solchen Server möglich ist, beträgt wahrscheinlich Millionen pro Sekunde. Die CPU befindet sich im Leerlauf, die Festplatte im Leerlauf.
Der Grund ist ganz klar, dass Mariadb genau wie MySQL interne Verzögerungen hat.

John
quelle
@Craftables Sie benötigen externe Entwicklung, es kann nicht in MySQL durchgeführt werden. Threads bedeutet, dass Sie mehrere Verbindungen zum Server verwenden und die Abfrage in mehrere Blöcke aufteilen (z. B. indem Sie sie nach Primärschlüssel in gerade Teile aufteilen). Mit dieser Methode konnte ich auf sehr großen Tabellen die 10.000-fache Leistung erzielen. Abfragen, die 40.000 Sekunden lang ausgeführt werden, können in 2-3 Minuten abgeschlossen werden, wenn Sie mehrere Threads verwenden und Ihr MySQL stark optimiert ist.
John
@ John Interessant und hat vielleicht einige wirklich nette Anwendungen ... aber ... Wenn Sie die Abfrage in mehrere Blöcke aufteilen, wie gehen Sie mit Transaktionen um? Betrachten Sie auch das folgende Szenario: Tabelle x enthält eine Spalte 'parent_id', die sich auf dieselbe Tabelle 'id' bezieht. Irgendwo in Ihren Daten haben Sie INSERT INTO x ( id, parent_id) VALUES (1, NULL). Einer der nächsten Wertesätze ist mit dieser Zeile verknüpft. Wenn Sie in Blöcke aufgeteilt werden und dieser Satz zu einem anderen Block gelangt, wird er möglicherweise vor dem ersten verarbeitet, wodurch der gesamte Prozess fehlschlägt. Irgendeine Idee, wie man damit umgeht?
Zozo
@zozo Dies ist nützlich für Masseneinfügungen und Massenabfragen. Transaktionen würden die Leistung ohnehin beeinträchtigen, da sie viel Datenpufferung enthalten. Sie können Transaktionen jedoch auch in Multithread-Einfügungen oder Abfragen verwenden.
John
-2

Mehrere Einsätze sind schneller, aber es sollte nicht sein. Ein weiterer Vorteil ist das Deaktivieren von Constraints-Checks, mit denen temporäre Einfügungen viel schneller ausgeführt werden. Es spielt keine Rolle, ob Ihr Tisch es hat oder nicht. Testen Sie beispielsweise das Deaktivieren von Fremdschlüsseln und genießen Sie die Geschwindigkeit:

SET FOREIGN_KEY_CHECKS=0;

Natürlich sollten Sie es nach dem Einfügen wieder einschalten durch:

SET FOREIGN_KEY_CHECKS=1;

Dies ist eine übliche Methode zum Einfügen großer Datenmengen. Die Datenintegrität kann unterbrochen werden, daher sollten Sie sich darum kümmern, bevor Sie Fremdschlüsselprüfungen deaktivieren.

MSS
quelle
1
Keine Ahnung, warum ppl dies aus zwei Gründen positiv bewertet hat: 1. Es hat nichts mit der Frage zu tun. 2. Es ist eine wirklich schlechte Idee (mit wenigen Ausnahmen - wie Dumping oder strukturelle Temperaturänderungen -, aber im Allgemeinen schlecht). Die Überprüfungen haben einen Grund: Sie dienen dazu, die Datenkonsistenz sicherzustellen. Sie verlangsamen die Arbeit, weil sie sicherstellen, dass Sie keine Daten einfügen oder anderweitig ändern, die Sie nicht sollten. Versuchen Sie, Abfragen richtig zu optimieren. In jeder geschäftskritischen Umgebung würde dies den Tod der App bedeuten, da unabhängig davon, wie vorsichtig Sie sind, die Dinge irgendwann scheitern werden.
Zozo
1
Vielleicht, aber diese Option ist äußerst effektiv beim Importieren großer Tabellen und sehr praktisch und könnte einigen Leuten eine Vorstellung davon geben, wie sie das Einfügen von Daten viel schneller machen können.
MSS