PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) in PostgreSQL?

9.5 und neuer:

PostgreSQL 9.5 und neuere Versionen unterstützen INSERT ... ON CONFLICT (key) DO UPDATE (und ON CONFLICT (key) DO NOTHING ), d.h. upsert.

Vergleich mit ON DUPLICATE KEY UPDATE .

Kurze Erklärung.

Informationen zur Verwendung finden Sie im Handbuch - insbesondere conflict_action -Klausel im Syntaxdiagramm und den erläuternden Text.

Im Gegensatz zu den unten angegebenen Lösungen für 9.4 und älter funktioniert diese Funktion mit mehreren widersprüchlichen Zeilen und erfordert keine exklusive Sperre oder eine Wiederholungsschleife.

Der Commit, der das Feature hinzufügt, ist hier und die Diskussion um seine Entwicklung ist hier.

Wenn Sie 9.5 verwenden und keine Abwärtskompatibilität benötigen, können Sie jetzt aufhören zu lesen .

9.4 und älter:

PostgreSQL hat kein eingebautes UPSERT (oder MERGE )-Einrichtung, und es ist angesichts der gleichzeitigen Nutzung sehr schwierig, dies effizient zu tun.

Dieser Artikel behandelt das Problem ausführlich.

Im Allgemeinen müssen Sie zwischen zwei Optionen wählen:

  • Individuelle Einfüge-/Aktualisierungsvorgänge in einer Wiederholungsschleife; oder
  • Sperre die Tabelle und führe eine Stapelzusammenführung durch

Wiederholungsschleife für einzelne Zeilen

Die Verwendung einzelner Zeilen-Upserts in einer Wiederholungsschleife ist die vernünftige Option, wenn Sie möchten, dass viele Verbindungen gleichzeitig versuchen, Einfügungen durchzuführen.

Die PostgreSQL-Dokumentation enthält eine nützliche Prozedur, mit der Sie dies in einer Schleife innerhalb der Datenbank tun können. Im Gegensatz zu den meisten naiven Lösungen schützt es vor verlorenen Updates und Einfügungsrennen. Es funktioniert nur in READ COMMITTED Modus und ist nur dann sicher, wenn es das einzige ist, was Sie in der Transaktion tun. Die Funktion funktioniert nicht richtig, wenn Auslöser oder sekundäre eindeutige Schlüssel eindeutige Verstöße verursachen.

Diese Strategie ist sehr ineffizient. Wann immer es möglich ist, sollten Sie Arbeit in die Warteschlange stellen und stattdessen eine Massen-Upsert wie unten beschrieben durchführen.

Viele Lösungsversuche für dieses Problem berücksichtigen Rollbacks nicht und führen daher zu unvollständigen Updates. Zwei Transaktionen rennen miteinander umher; einer davon erfolgreich INSERT s; der andere erhält einen doppelten Schlüsselfehler und führt ein UPDATE durch stattdessen. Das UPDATE Blöcke warten auf INSERT zurücksetzen oder festschreiben. Beim Zurücksetzen wird das UPDATE Die erneute Überprüfung der Bedingung stimmt mit null Zeilen überein, also obwohl das UPDATE Commits hat es nicht wirklich den Upsert gemacht, den Sie erwartet haben. Sie müssen die Anzahl der Ergebniszeilen überprüfen und es gegebenenfalls erneut versuchen.

Einige Lösungsversuche berücksichtigen auch keine SELECT-Rennen. Wenn Sie das Offensichtliche und Einfache versuchen:

-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.

BEGIN;

UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;

-- Remember, this is WRONG. Do NOT COPY IT.

INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);

COMMIT;

Wenn dann zwei gleichzeitig laufen, gibt es mehrere Fehlermodi. Eines ist das bereits besprochene Problem mit einem erneuten Update-Check. Ein anderer ist, wo beide UPDATE gleichzeitig Nullzeilen abgleichen und fortfahren. Dann machen beide das EXISTS Test, der vorher passiert das INSERT . Beide erhalten null Zeilen, also führen beide das INSERT aus . Einer schlägt mit einem doppelten Schlüsselfehler fehl.

Aus diesem Grund benötigen Sie eine Wiederholungsschleife. Sie denken vielleicht, dass Sie doppelte Schlüsselfehler oder verlorene Updates mit cleverem SQL verhindern können, aber das können Sie nicht. Sie müssen die Anzahl der Zeilen überprüfen oder doppelte Schlüsselfehler behandeln (je nach gewähltem Ansatz) und es erneut versuchen.

Bitte rollt dafür keine eigene Lösung. Wie bei Message Queuing ist es wahrscheinlich falsch.

Bulk-Upsert mit Sperre

Manchmal möchten Sie ein Bulk-Upsert durchführen, bei dem Sie einen neuen Datensatz haben, den Sie mit einem älteren vorhandenen Datensatz zusammenführen möchten. Das ist weitgehend effizienter als einzelne Reihen-Upserts und sollte wann immer möglich bevorzugt werden.

In diesem Fall gehen Sie normalerweise wie folgt vor:

  • CREATE a TEMPORARY Tabelle

  • COPY oder fügen Sie die neuen Daten per Bulk in die temporäre Tabelle ein

  • LOCK die Zieltabelle IN EXCLUSIVE MODE . Dadurch können andere Transaktionen SELECT werden , aber nehmen Sie keine Änderungen an der Tabelle vor.

  • Führen Sie ein UPDATE ... FROM durch vorhandener Datensätze mit den Werten in der temporären Tabelle;

  • Führen Sie ein INSERT durch von Zeilen, die noch nicht in der Zieltabelle vorhanden sind;

  • COMMIT , Freigabe der Sperre.

Verwenden Sie beispielsweise für das in der Frage angegebene Beispiel mehrwertige INSERT um die temporäre Tabelle zu füllen:

BEGIN;

CREATE TEMPORARY TABLE newvals(id integer, somedata text);

INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');

LOCK TABLE testtable IN EXCLUSIVE MODE;

UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;

INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;

COMMIT;

Verwandte Lektüre

  • UPSERT-Wiki-Seite
  • UPSERTisms in Postgres
  • Bei doppelter Aktualisierung in PostgreSQL einfügen?
  • http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
  • Upsert mit einer Transaktion
  • Ist SELECT oder INSERT in einer Funktion anfällig für Race-Conditions?
  • SQL MERGE im PostgreSQL-Wiki
  • Der idiomatischste Weg, UPSERT heutzutage in Postgresql zu implementieren

Was ist mit MERGE ?

SQL-Standard MERGE hat tatsächlich eine schlecht definierte Nebenläufigkeitssemantik und ist nicht zum Upserting geeignet, ohne zuerst eine Tabelle zu sperren.

Es ist eine wirklich nützliche OLAP-Anweisung zum Zusammenführen von Daten, aber eigentlich keine nützliche Lösung für gleichzeitigkeitssicheres Upsert. Es gibt viele Ratschläge für Leute, die andere DBMS verwenden, um MERGE zu verwenden für Upserts, aber es ist eigentlich falsch.

Andere Datenbanken:

  • INSERT ... ON DUPLICATE KEY UPDATE in MySQL
  • MERGE von MS SQL Server (aber siehe oben zu MERGE Probleme)
  • MERGE von Oracle (aber siehe oben zu MERGE Probleme)