9.5 und neuer:
PostgreSQL 9.5 und neuere Unterstützung INSERT ... ON CONFLICT UPDATE(und ON CONFLICT DO NOTHING), dh Upsert.
Vergleich mitON DUPLICATE KEY UPDATE .
Schnelle Erklärung .
Siehe Nutzung des manuellen - speziell die conflict_action Klausel in dem Syntax - Diagramm und den erläuternden Text .
Im Gegensatz zu den unten aufgeführten Lösungen für 9.4 und älter funktioniert diese Funktion mit mehreren widersprüchlichen Zeilen und erfordert keine exklusive Sperre oder Wiederholungsschleife.
Das Commit, das das Feature hinzufügt, ist hier und die Diskussion über seine Entwicklung ist hier .
Wenn Sie mit 9.5 arbeiten und nicht abwärtskompatibel sein müssen, können Sie jetzt aufhören zu lesen .
9.4 und älter:
PostgreSQL verfügt über keine integrierte UPSERT(oder MERGE) integrierte Funktion, und es ist sehr schwierig, dies bei gleichzeitiger Verwendung effizient durchzuführen.
Dieser Artikel beschreibt das Problem ausführlich .
Im Allgemeinen müssen Sie zwischen zwei Optionen wählen:
- Einzelne Einfüge- / Aktualisierungsvorgänge in einer Wiederholungsschleife; oder
- Sperren der Tabelle und Zusammenführen von Stapeln
Einzelne Zeilenwiederholungsschleife
Die Verwendung einzelner Zeilenumbrüche in einer Wiederholungsschleife ist die sinnvolle Option, wenn viele Verbindungen gleichzeitig versuchen sollen, 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 fügt Rennen ein. Es funktioniert nur im READ COMMITTEDModus und ist nur dann sicher, wenn es das einzige ist, was Sie in der Transaktion tun. Die Funktion funktioniert nicht richtig, wenn Trigger oder sekundäre eindeutige Schlüssel eindeutige Verstöße verursachen.
Diese Strategie ist sehr ineffizient. Wann immer es praktisch ist, sollten Sie die Arbeit in die Warteschlange stellen und stattdessen einen Bulk-Upsert durchführen, wie unten beschrieben.
Viele Lösungsversuche für dieses Problem berücksichtigen keine Rollbacks, sodass sie zu unvollständigen Aktualisierungen führen. Zwei Transaktionen laufen miteinander; einer von ihnen erfolgreich INSERTs; Der andere erhält einen doppelten Schlüsselfehler und führt UPDATEstattdessen einen aus. Die UPDATEBlöcke, die darauf warten, dass INSERTsie zurückgesetzt oder festgeschrieben werden. Wenn es zurückgesetzt wird, UPDATEstimmt die erneute Überprüfung der Bedingung mit null Zeilen überein. Obwohl UPDATEdie Festschreibungen nicht den erwarteten Upsert ausgeführt haben. Sie müssen die Anzahl der Ergebniszeilen überprüfen und 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 ausgeführt werden, gibt es mehrere Fehlermodi. Eines ist das bereits besprochene Problem bei einer erneuten Überprüfung des Updates. Eine andere ist, wo beide UPDATEgleichzeitig, null Zeilen übereinstimmen und fortfahren. Dann machen beide den EXISTSTest, der vor dem INSERT. Beide bekommen null Zeilen, also machen beide das INSERT. Einer schlägt mit einem doppelten Schlüsselfehler fehl.
Aus diesem Grund benötigen Sie eine Wiederholungsschleife. Sie könnten denken, dass Sie mit cleverem SQL doppelte Schlüsselfehler oder verlorene Updates verhindern können, aber Sie können nicht. Sie müssen die Zeilenanzahl überprüfen oder doppelte Schlüsselfehler behandeln (abhängig vom gewählten Ansatz) und es erneut versuchen.
Bitte rollen Sie keine eigene Lösung dafür. Wie bei der Nachrichtenwarteschlange ist es wahrscheinlich falsch.
Bulk Upsert mit Schloss
Manchmal möchten Sie einen Bulk-Upsert durchführen, bei dem Sie einen neuen Datensatz haben, den Sie in einen älteren vorhandenen Datensatz zusammenführen möchten. Dies ist weitaus effizienter als einzelne Zeilenumbrüche und sollte nach Möglichkeit bevorzugt werden.
In diesem Fall gehen Sie normalerweise wie folgt vor:
CREATEein TEMPORARYTisch
COPY oder fügen Sie die neuen Daten in großen Mengen in die temporäre Tabelle ein
LOCKdie Zieltabelle IN EXCLUSIVE MODE. Dies ermöglicht anderen Transaktionen, SELECTnimmt jedoch keine Änderungen an der Tabelle vor.
Führen Sie einen UPDATE ... FROMder vorhandenen Datensätze mit den Werten in der temporären Tabelle aus.
Führen Sie eine INSERTder Zeilen aus, die noch nicht in der Zieltabelle vorhanden sind.
COMMIT, das Schloss loslassen.
Beispiel: Für das in der Frage angegebene Beispiel wird INSERTdie temporäre Tabelle mit mehreren Werten gefüllt:
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
Was ist mit MERGE?
SQL-Standard hat MERGEtatsächlich eine schlecht definierte Parallelitätssemantik und ist nicht zum Upserting geeignet, ohne vorher eine Tabelle zu sperren.
Es ist eine wirklich nützliche OLAP-Anweisung für das Zusammenführen von Daten, aber keine nützliche Lösung für das gleichzeitige Upsert. Es gibt viele Ratschläge für Leute, die andere DBMS MERGEfür Upserts verwenden, aber es ist tatsächlich falsch.
Andere DBs: