it-swarm.com.de

Bulk/Batch-Update/Ups in PostgreSQL

Ich schreibe eine Django-ORM-Erweiterung, die versucht, Modelle zwischenzuspeichern und das Speichern von Modellen bis zum Ende der Transaktion zu verschieben. Es ist fast alles fertig, aber ich bin auf eine unerwartete Schwierigkeit in der SQL-Syntax gestoßen.

Ich bin kein großer Datenbankadministrator, aber nach meinem Verständnis funktionieren Datenbanken für viele kleine Abfragen nicht wirklich effizient. Einige größere Abfragen sind viel besser. Zum Beispiel ist es besser, große Einsätze (z. B. 100 Zeilen auf einmal) anstelle von 100 Einzeilen zu verwenden.

Nach allem, was ich sehen kann, liefert SQL wirklich keine Anweisung, um eine Stapelaktualisierung für eine Tabelle durchzuführen. Der Begriff scheint verwirrend zu sein, also erkläre ich, was ich damit meine. Ich habe ein Array von beliebigen Daten, wobei jeder Eintrag eine einzelne Zeile in einer Tabelle beschreibt. Ich möchte bestimmte Zeilen in der Tabelle aktualisieren, wobei jeweils Daten aus dem entsprechenden Eintrag im Array verwendet werden. Die Idee ist einer Batch-Insertion sehr ähnlich. 

Zum Beispiel: Meine Tabelle könnte zwei Spalten "id" und "some_col" haben. Das Array, das die Daten für eine Stapelaktualisierung beschreibt, besteht jetzt aus drei Einträgen (1, 'first updated'), (2, 'second updated') und (3, 'third updated'). Vor dem Update enthält die Tabelle Zeilen: (1, 'first'), (2, 'second'), (3, 'third').

Ich bin über diesen Beitrag gekommen:

Warum sind Batch-Inserts/Updates schneller? Wie funktionieren Batch-Updates?

was scheint zu tun was ich will, aber ich kann die Syntax am Ende nicht wirklich herausfinden.

Ich könnte auch alle Zeilen löschen, die aktualisiert werden müssen, und sie mit einer Batch-Einfügung erneut einfügen. Allerdings kann ich kaum glauben, dass dies tatsächlich besser wäre.

Ich arbeite mit PostgreSQL 8.4, daher sind hier auch einige gespeicherte Prozeduren möglich. Da ich jedoch beabsichtige, das Projekt letztendlich zu öffnen, sind alle tragbaren Ideen oder Möglichkeiten, das gleiche auf einem anderen RDBMS zu tun, sehr willkommen.

Folgefrage: Wie führt man eine Batch-Anweisung "insert-or-update"/"upsert" aus?

Testergebnisse

Ich habe 100x-mal 10 Einfügeoperationen durchgeführt, die sich auf 4 verschiedene Tabellen verteilen (insgesamt also 1000 Einfügungen). Ich habe auf Django 1.3 mit einem PostgreSQL 8.4-Backend getestet.

Das sind die Ergebnisse:

  • Alle Operationen, die über Django ORM ausgeführt werden - jeder Durchlauf ~ 2.45 Sekunden,
  • Dieselben Operationen, jedoch ohne Django ORM - jeder Durchlauf ~ 1.48 Sekunden,
  • Fügen Sie nur Operationen ein, ohne die Datenbank nach Sequenzwerten ~ 0.72 Sekunden zu fragen.
  • Nur Einfügeoperationen, ausgeführt in Blöcken von 10 (insgesamt 100 Blöcke) ~ 0,19 Sekunden,
  • Nur Einfügeoperationen, ein großer Ausführungsblock ~ 0,13 Sekunden.
  • Nur Einfügeoperationen, ungefähr 250 Anweisungen pro Block, ~ 0,12 Sekunden.

Fazit: Führen Sie so viele Operationen wie möglich in einer einzigen Verbindung aus.execute (). Django selbst führt einen erheblichen Aufwand ein.

Haftungsausschluss: Ich habe außer den Standard-Primärschlüssel-Indizes keine Indizes eingeführt. Daher könnten Einfügevorgänge möglicherweise schneller ausgeführt werden.

39
julkiewicz

Ich habe 3 Strategien für Batch-Transaktionsarbeit verwendet:

  1. Generieren Sie schnell SQL-Anweisungen, verketten Sie diese mit Semikolons und senden Sie die Anweisungen in einem Schritt. Ich habe bis zu 100 Inserts auf diese Weise gemacht, und es war ziemlich effizient (gegen Postgres).
  2. JDBC verfügt über integrierte Stapelfunktionen, sofern konfiguriert. Wenn Sie Transaktionen generieren, können Sie Ihre JDBC-Anweisungen bereinigen, damit sie in einem Schritt ausgeführt werden. Diese Taktik erfordert weniger Datenbankaufrufe, da die Anweisungen alle in einem Stapel ausgeführt werden.
  3. Hibernate unterstützt auch das JDBC-Batching gemäß dem Beispiel des vorherigen Beispiels. In diesem Fall führen Sie jedoch eine flush()-Methode für Hibernate Session aus, nicht für die zugrunde liegende JDBC-Verbindung. Es bewirkt dasselbe wie das JDBC-Batching.

Übrigens unterstützt Hibernate auch eine Stapelstrategie beim Abholen von Sammlungen. Wenn Sie eine Auflistung mit @BatchSize kommentieren, verwendet Hibernate beim Abrufen von Assoziationen IN anstelle von =. Dadurch werden weniger SELECT-Anweisungen zum Laden der Auflistungen benötigt.

12
atrain

Bulk-Einsatz

Sie können die Masseneinfügung von drei Spalten von Ketema ändern:

INSERT INTO "table" (col1, col2, col3)
  VALUES (11, 12, 13) , (21, 22, 23) , (31, 32, 33);

Es wird:

INSERT INTO "table" (col1, col2, col3)
  VALUES (unnest(array[11,21,31]), 
          unnest(array[12,22,32]), 
          unnest(array[13,23,33]))

Werte durch Platzhalter ersetzen:

INSERT INTO "table" (col1, col2, col3)
  VALUES (unnest(?), unnest(?), unnest(?))

Sie müssen Arrays oder Listen als Argumente an diese Abfrage übergeben. Dies bedeutet, dass Sie riesige Bulk-Inserts ohne String-Verkettung ausführen können (und alle Hazzles und Gefahren: SQL-Injektion und Anführungszeichen).

Bulk-Update

PostgreSQL hat die FROM-Erweiterung zu UPDATE hinzugefügt. Sie können es auf folgende Weise verwenden:

update "table" 
  set value = data_table.new_value
  from 
    (select unnest(?) as key, unnest(?) as new_value) as data_table
  where "table".key = data_table.key;

Dem Handbuch fehlt eine gute Erklärung, aber es gibt ein Beispiel auf der Mailingliste postgresql-admin . Ich habe versucht, darauf einzugehen:

create table tmp
(
  id serial not null primary key,
  name text,
  age integer
);

insert into tmp (name,age) 
values ('keith', 43),('leslie', 40),('bexley', 19),('casey', 6);

update tmp set age = data_table.age
from
(select unnest(array['keith', 'leslie', 'bexley', 'casey']) as name, 
        unnest(array[44, 50, 10, 12]) as age) as data_table
where tmp.name = data_table.name;

Es gibt auch otherposts auf StackExchange, das UPDATE...FROM.. mithilfe einer VALUES-Klausel anstelle einer Unterabfrage erläutert. Sie können leichter lesbar sein, sind aber auf eine feste Anzahl von Zeilen beschränkt.

43
hagello

Bulk-Inserts können als solche ausgeführt werden:

INSERT INTO "table" ( col1, col2, col3)
  VALUES ( 1, 2, 3 ) , ( 3, 4, 5 ) , ( 6, 7, 8 );

Fügt 3 Reihen ein.

Mehrfachaktualisierungen werden durch den SQL-Standard definiert, aber nicht in PostgreSQL implementiert.

Zitat: 

"Nach dem Standard sollte die Spaltenlistensyntax die Zuweisung einer Liste .__ von Spalten aus einem einzelnen Zeilenwertausdruck ermöglichen, z. B.

UPDATE-Konten SET (contact_last_name, contact_first_name) = (SELECT last_name, vorname FROM salesmen WHERE salesmen.id = accounts.sales_id);

Referenz: http://www.postgresql.org/docs/9.0/static/sql-update.html

12
Ketema

es ist ziemlich schnell, Json in das Recordset zu füllen (postgresql 9.3+)

big_list_of_tuples = [
    (1, "123.45"),
    ...
    (100000, "678.90"),
]

connection.execute("""
    UPDATE mytable
    SET myvalue = Q.myvalue
    FROM (
        SELECT (value->>0)::integer AS id, (value->>1)::decimal AS myvalue 
        FROM json_array_elements(%s)
    ) Q
    WHERE mytable.id = Q.id
    """, 
    [json.dumps(big_list_of_tuples)]
)
2
nogus

Deaktivieren Sie Autocommit und führen Sie am Ende nur ein Commit aus. In plain SQL bedeutet dies, dass BEGIN am Anfang und COMMIT am Ende ausgegeben werden. Sie müssten eine -Funktion erstellen, um einen tatsächlichen Aufwärtstrend durchzuführen.

0
aliasmrchips