9.5 og nyere:
PostgreSQL 9.5 og nyere understøttelse INSERT ... ON CONFLICT (key) DO UPDATE
(og ON CONFLICT (key) DO NOTHING
), dvs. upsert.
Sammenligning med ON DUPLICATE KEY UPDATE
.
Hurtig forklaring.
For brug se manualen - specifikt conflict_action klausul i syntaksdiagrammet og den forklarende tekst.
I modsætning til løsningerne til 9.4 og ældre, der er angivet nedenfor, fungerer denne funktion med flere modstridende rækker, og den kræver ikke eksklusiv låsning eller en genforsøgsløkke.
Forpligtelsen til at tilføje funktionen er her, og diskussionen omkring dens udvikling er her.
Hvis du er på 9.5 og ikke behøver at være bagudkompatibel, kan du stoppe med at læse nu .
9.4 og ældre:
PostgreSQL har ikke nogen indbygget UPSERT
(eller MERGE
) facilitet, og at gøre det effektivt i lyset af samtidig brug er meget vanskeligt.
Denne artikel diskuterer problemet i nyttige detaljer.
Generelt skal du vælge mellem to muligheder:
- Individuelle indsættelses-/opdateringsoperationer i en genforsøgsløkke; eller
- Låser bordet og laver batchfletning
Individuel række genforsøg loop
Brug af individuelle række-upserts i en genforsøgsløkke er den rimelige mulighed, hvis du vil have mange forbindelser, der samtidig forsøger at udføre indsættelser.
PostgreSQL-dokumentationen indeholder en nyttig procedure, der lader dig gøre dette i en løkke inde i databasen. Den beskytter mod tabte opdateringer og indsæt racer, i modsætning til de fleste naive løsninger. Det vil kun virke i READ COMMITTED
tilstand og er kun sikker, hvis det er det eneste, du gør i transaktionen. Funktionen fungerer ikke korrekt, hvis triggere eller sekundære unikke nøgler forårsager unikke overtrædelser.
Denne strategi er meget ineffektiv. Når det er praktisk, bør du stille arbejdet i kø og i stedet lave en bulk-upsert som beskrevet nedenfor.
Mange forsøg på løsninger på dette problem tager ikke hensyn til tilbagerulninger, så de resulterer i ufuldstændige opdateringer. To transaktioner kapløb med hinanden; en af dem INSERT
s; den anden får en dubletnøglefejl og laver en UPDATE
i stedet. UPDATE
blokke, der venter på INSERT
at rulle tilbage eller begå. Når den ruller tilbage, vises UPDATE
betingelsesgenkontrol matcher nul rækker, så selvom UPDATE
begår, at det faktisk ikke har gjort det oprør, du forventede. Du er nødt til at kontrollere resultatrækketællingerne og prøve igen, hvor det er nødvendigt.
Nogle forsøg på løsninger tager heller ikke hensyn til SELECT-løb. Hvis du prøver det åbenlyse og enkle:
-- 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;
så når to kører på én gang er der flere fejltilstande. Det ene er det allerede diskuterede problem med en opdateringskontrol. En anden er, hvor både UPDATE
på samme tid, matchende nul rækker og fortsætter. Så laver de begge EXISTS
test, som sker før INSERT
. Begge får nul rækker, så begge udfører INSERT
. Man fejler med en dubletnøglefejl.
Det er derfor, du har brug for en genforsøgsløkke. Du tror måske, at du kan forhindre duplikerede nøglefejl eller mistede opdateringer med smart SQL, men det kan du ikke. Du skal kontrollere rækkeantal eller håndtere duplikerede nøglefejl (afhængigt af den valgte tilgang) og prøve igen.
Lad være med at rulle din egen løsning til dette. Ligesom med beskedkø, er det sandsynligvis forkert.
Masseopskæring med lås
Nogle gange vil man lave en bulk upsert, hvor man har et nyt datasæt, som man vil flette ind i et ældre eksisterende datasæt. Dette er svært mere effektiv end individuelle row upserts og bør foretrækkes, når det er praktisk muligt.
I dette tilfælde følger du typisk følgende proces:
-
CREATE
enTEMPORARY
bord -
COPY
eller masseindsæt de nye data i den midlertidige tabel -
LOCK
måltabellenIN EXCLUSIVE MODE
. Dette tillader andre transaktioner atSELECT
, men foretag ingen ændringer i tabellen. -
Foretag en
UPDATE ... FROM
af eksisterende poster ved hjælp af værdierne i temp-tabellen; -
Lav en
INSERT
rækker, der ikke allerede findes i måltabellen; -
COMMIT
, udløser låsen.
For eksempel, for eksemplet givet i spørgsmålet, ved at bruge INSERT
med flere værdier for at udfylde den midlertidige tabel:
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;
Relateret læsning
- UPSERT wiki-side
- UPSERTisms i Postgres
- Indsæt, ved dubletopdatering i PostgreSQL?
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- Oprør med en transaktion
- Er SELECT eller INSERT i en funktion, der er tilbøjelig til løbsforhold?
- SQL
MERGE
på PostgreSQL-wikien - Mest idiomatisk måde at implementere UPSERT i Postgresql i dag
Hvad med MERGE
?
SQL-standard MERGE
har faktisk dårligt defineret concurrency semantik og er ikke egnet til upserting uden at låse en tabel først.
Det er en virkelig nyttig OLAP-erklæring til datasammenfletning, men det er faktisk ikke en nyttig løsning til samtidighedssikker upsert. Der er masser af råd til folk, der bruger andre DBMS'er til at bruge MERGE
for upserts, men det er faktisk forkert.
Andre DB'er:
INSERT ... ON DUPLICATE KEY UPDATE
i MySQLMERGE
fra MS SQL Server (men se ovenfor omMERGE
problemer)MERGE
fra Oracle (men se ovenfor omMERGE
problemer)