sql >> Database >  >> RDS >> PostgreSQL

Hoe te UPSERT (MERGE, INSERT ... OP DUPLICATE UPDATE) in PostgreSQL?

9.5 en nieuwer:

PostgreSQL 9.5 en nieuwer ondersteunen INSERT ... ON CONFLICT (key) DO UPDATE (en ON CONFLICT (key) DO NOTHING ), d.w.z. upsert.

Vergelijking met ON DUPLICATE KEY UPDATE .

Snelle uitleg.

Voor gebruik zie de handleiding - met name de conflict_action clausule in het syntaxisdiagram en de verklarende tekst.

In tegenstelling tot de oplossingen voor 9.4 en ouder die hieronder worden gegeven, werkt deze functie met meerdere conflicterende rijen en is er geen exclusieve vergrendeling of een nieuwe poging nodig.

De toezegging om de functie toe te voegen is hier en de discussie over de ontwikkeling ervan is hier.

Als je 9.5 gebruikt en niet achterwaarts compatibel hoeft te zijn, kun je nu stoppen met lezen .

9.4 en ouder:

PostgreSQL heeft geen ingebouwde UPSERT (of MERGE ) faciliteit, en het efficiënt doen in het licht van gelijktijdig gebruik is erg moeilijk.

Dit artikel bespreekt het probleem in nuttige details.

Over het algemeen moet u kiezen tussen twee opties:

  • Individuele invoeg-/bijwerkbewerkingen in een lus voor opnieuw proberen; of
  • De tabel vergrendelen en batch samenvoegen

Loop opnieuw proberen in afzonderlijke rij

Het gebruik van individuele rij-upserts in een lus voor opnieuw proberen is de redelijke optie als u wilt dat veel verbindingen tegelijkertijd proberen invoegingen uit te voeren.

De PostgreSQL-documentatie bevat een handige procedure waarmee u dit in een lus in de database kunt doen. Het beschermt tegen verloren updates en invoegraces, in tegenstelling tot de meeste naïeve oplossingen. Het werkt alleen in READ COMMITTED modus en is alleen veilig als dit het enige is dat u in de transactie doet. De functie werkt niet correct als triggers of secundaire unieke sleutels unieke schendingen veroorzaken.

Deze strategie is erg inefficiënt. Als het praktisch is, moet je het werk in de rij zetten en in plaats daarvan een bulk-upsert doen zoals hieronder beschreven.

Veel pogingen tot oplossingen voor dit probleem houden geen rekening met terugdraaien, dus resulteren ze in onvolledige updates. Twee transacties racen met elkaar; een van hen succesvol INSERT s; de andere krijgt een dubbele sleutelfout en doet een UPDATE in plaats van. De UPDATE blokken wachten op de INSERT terugdraaien of vastleggen. Wanneer het teruggaat, wordt de UPDATE de hercontrole van de voorwaarde komt overeen met nul rijen, dus ook al is de UPDATE commits heeft het niet echt de ophef veroorzaakt die je had verwacht. U moet het aantal rijen met resultaten controleren en zo nodig opnieuw proberen.

Bij sommige geprobeerde oplossingen wordt ook geen rekening gehouden met SELECT-races. Als je het voor de hand liggende en eenvoudige probeert:

-- 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;

als er dan twee tegelijk worden uitgevoerd, zijn er verschillende storingsmodi. Een daarvan is het reeds besproken probleem met een update-hercontrole. Een andere is waar zowel UPDATE tegelijkertijd nul rijen matchen en doorgaan. Dan doen ze allebei de EXISTS test, die vóór . gebeurt de INSERT . Beide krijgen nul rijen, dus beide doen de INSERT . Een mislukt met een dubbele sleutelfout.

Dit is waarom je een re-try-lus nodig hebt. U denkt misschien dat u dubbele sleutelfouten of verloren updates kunt voorkomen met slimme SQL, maar dat is niet zo. U moet het aantal rijen controleren of dubbele sleutelfouten afhandelen (afhankelijk van de gekozen aanpak) en het opnieuw proberen.

Rol hier alsjeblieft niet je eigen oplossing voor. Net als bij het in de wachtrij plaatsen van berichten, is het waarschijnlijk verkeerd.

Bulk upsert met slot

Soms wil je een bulk-upsert doen, waarbij je een nieuwe dataset hebt die je wilt samenvoegen met een oudere bestaande dataset. Dit is enorm efficiënter dan individuele rij-upserts en zou de voorkeur moeten hebben wanneer dit praktisch is.

In dit geval volgt u meestal het volgende proces:

  • CREATE a TEMPORARY tafel

  • COPY of voeg de nieuwe gegevens in bulk in de tijdelijke tabel

  • LOCK de doeltabel IN EXCLUSIVE MODE . Dit staat andere transacties toe om SELECT , maar breng geen wijzigingen aan in de tabel.

  • Doe een UPDATE ... FROM van bestaande records met behulp van de waarden in de tijdelijke tabel;

  • Doe een INSERT rijen die nog niet bestaan ​​in de doeltabel;

  • COMMIT , het slot ontgrendelen.

Bijvoorbeeld, voor het voorbeeld gegeven in de vraag, met behulp van meerwaardige INSERT om de tijdelijke tabel te vullen:

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;

Gerelateerde lectuur

  • UPSERT-wikipagina
  • UPSERTismen in Postgres
  • Invoegen, bij dubbele update in PostgreSQL?
  • http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
  • Upsert met een transactie
  • Is SELECT of INSERT in een functie die vatbaar is voor race-omstandigheden?
  • SQL MERGE op de PostgreSQL-wiki
  • De meest idiomatische manier om UPSERT tegenwoordig in Postgresql te implementeren

Hoe zit het met MERGE ?

SQL-standaard MERGE heeft eigenlijk slecht gedefinieerde concurrency-semantiek en is niet geschikt voor upserting zonder eerst een tabel te vergrendelen.

Het is een echt nuttige OLAP-instructie voor het samenvoegen van gegevens, maar het is niet echt een nuttige oplossing voor gelijktijdigheidsveilige upsert. Er is veel advies aan mensen die andere DBMS'en gebruiken om MERGE te gebruiken voor upserts, maar het is eigenlijk verkeerd.

Andere DB's:

  • INSERT ... ON DUPLICATE KEY UPDATE in MySQL
  • MERGE van MS SQL Server (maar zie hierboven over MERGE problemen)
  • MERGE van Oracle (maar zie hierboven over MERGE problemen)


  1. Gegevens exporteren naar CSV-bestand in Oracle met behulp van PL SQL Procedure

  2. Het referentiegegevenspatroon:uitbreidbaar en flexibel

  3. Gegevens groeperen met de functies OVER en PARTITION BY

  4. Database laten vallen met behulp van TSQL en GUI - SQL Server / TSQL-zelfstudie, deel 25