web-dev-qa-db-de.com

Masseneinfügung in Java mit Batch-Update für vorbereitete Anweisungen

Ich versuche, ein resultSet in Java mit etwa 50.000 Zeilen mit 10 Spalten zu füllen Dann füge ich es mit der batchExecute-Methode von PreparedStatement in eine andere Tabelle ein.

Um den Prozess zu beschleunigen, habe ich einige Nachforschungen angestellt und festgestellt, dass fetchSize beim Einlesen von Daten in resultSet eine wichtige Rolle spielt.

Ein sehr niedriges fetchSize-Verhältnis kann zu zu vielen Server-Trips führen und ein sehr hohes fetchSize-Netzwerk kann die Netzwerkressourcen blockieren. Daher habe ich ein wenig experimentiert und die optimale Größe für meine Infrastruktur festgelegt.

Ich lese dieses resultSet und erstelle Einfügeanweisungen zum Einfügen in eine andere Tabelle einer anderen Datenbank.

So etwas (nur ein Beispiel, kein echter Code):

for (i=0 ; i<=50000 ; i++) {
    statement.setString(1, "[email protected]");
    statement.setLong(2, 1);
    statement.addBatch();
}
statement.executeBatch();
  • Wird die Methode executeBatch versuchen, alle Daten gleichzeitig zu senden?
  • Gibt es eine Möglichkeit, die Chargengröße zu definieren?
  • Gibt es eine bessere Möglichkeit, den Vorgang des Masseneinlegens zu beschleunigen?

Ist es besser, während der Massenaktualisierung (50.000 Zeilen, 10 Cols) eine aktualisierbare ResultSet oder PreparedStaement mit Batch-Ausführung zu verwenden?

28
Mrinmoy

Ich werde nacheinander auf Ihre Fragen eingehen.

  • Wird die Methode executeBatch versuchen, alle Daten gleichzeitig zu senden?

Dies kann bei jedem JDBC-Treiber variieren, aber die wenigen, die ich untersucht habe, werden jeden Stapeleintrag durchlaufen und die Argumente zusammen mit dem vorbereiteten Anweisungshandle jedes Mal zur Ausführung an die Datenbank senden. In Ihrem obigen Beispiel würden also 50.000 Ausführungen der vorbereiteten Anweisung mit 50.000 Argumentpaaren ausgeführt werden, aber diese 50.000 Schritte können in einer "inneren Schleife" auf niedrigerer Ebene ausgeführt werden, in der die Zeitersparnis eingesetzt wird Die Analogie ist ziemlich lang, es ist, als würde man aus dem "User-Modus" in den "Kernel-Modus" wechseln und dort die gesamte Ausführungsschleife ausführen. Sie sparen die Kosten für das Eintauchen in und aus dem untergeordneten Modus für jeden Stapeleintrag.

  • Gibt es eine Möglichkeit, die Stapelgröße zu definieren

Sie haben es hier implizit definiert, indem Sie 50.000 Argumentsätze vor dem Ausführen des Stapels über Statement#executeBatch() einschieben. Eine Stapelgröße von Eins ist genauso gültig.

  • Gibt es eine bessere Möglichkeit, den Vorgang des Masseneinfügens zu beschleunigen?

Erwägen Sie das explizite Öffnen einer Transaktion vor dem Batch-Einfügen, und machen Sie sie anschließend fest. Lassen Sie nicht zu, dass die Datenbank oder der JDBC-Treiber eine Transaktionsgrenze für jeden Einfügungsschritt im Stapel festlegen. Sie können die JDBC-Schicht mit der Methode Connection#setAutoCommit(boolean) steuern. Trennen Sie zuerst die Verbindung aus dem Auto-Commit-Modus, füllen Sie dann Ihre Stapel auf, starten Sie eine Transaktion, führen Sie den Stapel aus, und bestätigen Sie die Transaktion über Connection#commit() .

In diesem Rat wird davon ausgegangen, dass Ihre Einfügungen nicht mit gleichzeitigen Schreibern konkurrieren, und es wird davon ausgegangen, dass diese Transaktionsgrenzen Ihnen ausreichend konsistente Werte bieten, die aus Ihren Quelltabellen gelesen werden, um in den Einfügungen verwendet zu werden. Wenn dies nicht der Fall ist, ziehen Sie die Richtigkeit der Geschwindigkeit vor.

  • Ist es besser, bei der Batch-Ausführung ein aktualisierbares ResultSet oder PreparedStatement zu verwenden?

Nichts geht über das Testen mit Ihrem JDBC-Treiber der Wahl, aber ich gehe davon aus, dass letzteres - PreparedStatement und Statement#executeBatch() - hier gewinnen werden. Das Anweisungshandle kann eine zugeordnete Liste oder ein Array von "Stapelargumenten" enthalten, wobei jeder Eintrag der Argumentsatz ist, der zwischen Aufrufen von Statement#executeBatch() und Statement#addBatch() (oder Statement#clearBatch()) bereitgestellt wird. Die Liste wird mit jedem Aufruf von addBatch() größer und wird erst gelöscht, wenn Sie executeBatch() aufrufen. Daher wirkt die Statement-Instanz wirklich als Argumentpuffer. Sie tauschen Speicher aus Bequemlichkeitsgründen aus (verwenden Sie die Statement-Instanz anstelle Ihres eigenen externen Argumentsatzpuffers).

Auch hier sollten Sie diese Antworten als allgemein und spekulativ betrachten, solange wir nicht über einen spezifischen JDBC-Treiber sprechen. Jeder Treiber variiert in seiner Raffinesse und jeder wird in seinen Optimierungen variieren.

43
seh

Der Batch wird in "Alles gleichzeitig" durchgeführt - darum haben Sie ihn gebeten.

50.000 scheinen ein bisschen groß zu sein, um in einem Anruf versucht zu werden. Ich würde es in kleinere Stücke von 1.000 aufteilen, wie folgt:

final int BATCH_SIZE = 1000;
for (int i = 0; i < DATA_SIZE; i++) {
  statement.setString(1, "[email protected]");
  statement.setLong(2, 1);
  statement.addBatch();
  if (i % BATCH_SIZE == BATCH_SIZE - 1)
    statement.executeBatch();
}
if (DATA_SIZE % BATCH_SIZE != 0)
  statement.executeBatch();

50.000 Reihen sollten nicht länger als ein paar Sekunden dauern.

12
Bohemian

Wenn nur Daten aus einer/mehreren Tabellen in der DB in diese Tabelle eingefügt werden sollen und kein Eingriff (Änderungen am Resultset), dann rufen Sie statement.executeUpdate(SQL) auf, um INSERT-SELECT statment auszuführen Es gibt keinen Aufwand. Keine Daten, die außerhalb des DBs liegen, und der gesamte Vorgang ist im DB nicht in der Anwendung enthalten.

1
LINQ Newbee

Eine nicht protokollierte Massenaktualisierung bietet Ihnen nicht die verbesserte Leistung, die Sie so wünschen, wie Sie es wollen. Siehe this

0
Lekkie