Eine sehr häufig gestellte Frage ist hier, wie ein Upsert durchgeführt wird. Dies wird von MySQL aufgerufen INSERT ... ON DUPLICATE UPDATE
und der Standard unterstützt dies als Teil der MERGE
Operation.
Wie machen Sie das, da PostgreSQL es nicht direkt unterstützt (vor S. 9.5)? Folgendes berücksichtigen:
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
Nun stell dir vor , dass Sie wollen „Upsert“ die Tupel (2, 'Joe')
, (3, 'Alan')
, so dass die neuen Tabelleninhalt wäre:
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
Darüber reden die Leute, wenn sie über eine upsert
. Entscheidend ist, dass jeder Ansatz sicher ist, wenn mehrere Transaktionen an derselben Tabelle ausgeführt werden - entweder durch explizite Sperrung oder auf andere Weise gegen die daraus resultierenden Rennbedingungen.
Dieses Thema wird ausführlich unter Einfügen, über doppelte Aktualisierung in PostgreSQL? Aber es geht um Alternativen zur MySQL-Syntax, und im Laufe der Zeit sind einiges an nicht verwandten Details gewachsen. Ich arbeite an endgültigen Antworten.
Diese Techniken sind auch nützlich für "Einfügen, wenn nicht vorhanden, sonst nichts tun", dh "Einfügen ... bei Duplikatschlüssel ignorieren".
quelle
Antworten:
9.5 und neuer:
PostgreSQL 9.5 und neuere Unterstützung
INSERT ... ON CONFLICT UPDATE
(undON CONFLICT DO NOTHING
), dh Upsert.Vergleich mit
ON 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
(oderMERGE
) 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 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ührtUPDATE
stattdessen einen aus. DieUPDATE
Blöcke, die darauf warten, dassINSERT
sie zurückgesetzt oder festgeschrieben werden. Wenn es zurückgesetzt wird,UPDATE
stimmt die erneute Überprüfung der Bedingung mit null Zeilen überein. ObwohlUPDATE
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:
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 denEXISTS
Test, der vor demINSERT
. Beide bekommen null Zeilen, also machen beide dasINSERT
. 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
einTEMPORARY
TischCOPY
oder fügen Sie die neuen Daten in großen Mengen in die temporäre Tabelle einLOCK
die ZieltabelleIN 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:Verwandte Lektüre
MERGE
im PostgreSQL-WikiWas 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:
INSERT ... ON DUPLICATE KEY UPDATE
in MySQLMERGE
von MS SQL Server (aber siehe oben überMERGE
Probleme)MERGE
von Oracle (aber siehe oben überMERGE
Probleme)quelle
MERGE
für SQL Server und Oracle verwendet werden, sind falsch und anfällig für Rennbedingungen, wie oben erwähnt. Sie müssen sich jedes DBMS genauer ansehen, um herauszufinden, wie Sie damit umgehen sollen. Ich kann wirklich nur Ratschläge zu PostgreSQL geben. Die einzige Möglichkeit, ein sicheres mehrzeiliges Upsert unter PostgreSQL durchzuführen, besteht darin, dem Kernserver Unterstützung für natives Upsert hinzuzufügen.Ich versuche, mit einer anderen Lösung für das Problem der einzelnen Einfügung mit den Versionen vor 9.5 von PostgreSQL beizutragen. Die Idee ist einfach, zuerst zu versuchen, das Einfügen durchzuführen, und falls der Datensatz bereits vorhanden ist, ihn zu aktualisieren:
Beachten Sie, dass diese Lösung nur angewendet werden kann, wenn keine Zeilen in der Tabelle gelöscht werden .
Ich weiß nichts über die Effizienz dieser Lösung, aber es scheint mir vernünftig genug.
quelle
insert on update
Hier sind einige Beispiele für
insert ... on conflict ...
( S. 9.5+ ):quelle
SQLAlchemy Upsert für Postgres> = 9,5
Da der große Beitrag oben viele verschiedene SQL-Ansätze für Postgres-Versionen behandelt (nicht nur Nicht-9.5 wie in der Frage), möchte ich hinzufügen, wie dies in SQLAlchemy gemacht wird, wenn Sie Postgres 9.5 verwenden. Anstatt Ihren eigenen Upsert zu implementieren, können Sie auch die Funktionen von SQLAlchemy verwenden (die in SQLAlchemy 1.1 hinzugefügt wurden). Persönlich würde ich empfehlen, diese zu verwenden, wenn möglich. Nicht nur aus praktischen Gründen, sondern auch, weil PostgreSQL damit alle möglicherweise auftretenden Rennbedingungen bewältigen kann.
Cross-Posting von einer anderen Antwort, die ich gestern gegeben habe ( https://stackoverflow.com/a/44395983/2156909 )
SQLAlchemy unterstützt
ON CONFLICT
jetzt mit zwei Methodenon_conflict_do_update()
undon_conflict_do_nothing()
:Kopieren aus der Dokumentation:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert
quelle
Getestet auf Postgresql 9.3
quelle
SERIALIZABLE
Isolation verwenden, erhalten Sie einen Abbruch mit einem Serialisierungsfehler, andernfalls erhalten Sie wahrscheinlich eine eindeutige Verletzung. Upsert nicht neu erfinden, die Neuerfindung wird falsch sein. Verwenden SieINSERT ... ON CONFLICT ...
. Wenn Ihr PostgreSQL zu alt ist, aktualisieren Sie es.INSERT ... ON CLONFLICT ...
ist nicht zum Massenladen vorgesehen. Von Ihrem Beitrag aus ist dasLOCK TABLE testtable IN EXCLUSIVE MODE;
innerhalb eines CTE eine Problemumgehung, um atomare Dinge zu erhalten. Nein ?insert ... where not exists ...
natürlich eine oder ähnliche Aktionen auszuführen.Da diese Frage geschlossen wurde, poste ich hier, wie Sie es mit SQLAlchemy machen. Durch Rekursion wird ein Masseneinsatz oder eine Aktualisierung wiederholt, um die Rennbedingungen zu bekämpfen und Validierungsfehler .
Zuerst die Importe
Jetzt funktioniert ein paar Helfer
Und schließlich die Upsert-Funktion
So verwenden Sie es
Dies hat den Vorteil,
bulk_save_objects
dass Beziehungen (Fehlerprüfung usw.) beim Einfügen verarbeitet werden können (im Gegensatz zu Massenoperationen ).quelle
SERIALIZABLE
Transaktionen verwenden und Serialisierungsfehler behandeln, aber es ist langsam. Sie benötigen eine Fehlerbehandlung und eine Wiederholungsschleife. Siehe meine Antwort und den Abschnitt "Verwandte Lektüre" darin.