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 COMMITTED
Modus 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 INSERT
s; Der andere erhält einen doppelten Schlüsselfehler und führt UPDATE
stattdessen einen aus. Die UPDATE
Blöcke, die darauf warten, dass INSERT
sie zurückgesetzt oder festgeschrieben werden. Wenn es zurückgesetzt wird, UPDATE
stimmt die erneute Überprüfung der Bedingung mit null Zeilen überein. Obwohl UPDATE
die 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 UPDATE
gleichzeitig, null Zeilen übereinstimmen und fortfahren. Dann machen beide den EXISTS
Test, 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:
CREATE
ein TEMPORARY
Tisch
COPY
oder fügen Sie die neuen Daten in großen Mengen in die temporäre Tabelle ein
LOCK
die Zieltabelle IN EXCLUSIVE MODE
. Dies ermöglicht anderen Transaktionen, SELECT
nimmt jedoch keine Änderungen an der Tabelle vor.
Führen Sie einen UPDATE ... FROM
der vorhandenen Datensätze mit den Werten in der temporären Tabelle aus.
Führen Sie eine INSERT
der Zeilen aus, die noch nicht in der Zieltabelle vorhanden sind.
COMMIT
, das Schloss loslassen.
Beispiel: Für das in der Frage angegebene Beispiel wird INSERT
die 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 MERGE
tatsä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 MERGE
für Upserts verwenden, aber es ist tatsächlich falsch.
Andere DBs: