9.5 och nyare:
PostgreSQL 9.5 och nyare stöd INSERT ... ON CONFLICT (key) DO UPDATE
(och ON CONFLICT (key) DO NOTHING
), d.v.s. upsert.
Jämförelse med ON DUPLICATE KEY UPDATE
.
Snabb förklaring.
För användning se manualen - specifikt conflict_action sats i syntaxdiagrammet och den förklarande texten.
Till skillnad från lösningarna för 9.4 och äldre som ges nedan, fungerar den här funktionen med flera motstridiga rader och den kräver ingen exklusiv låsning eller en slinga igen.
Åtagandet som lägger till funktionen är här och diskussionen kring dess utveckling är här.
Om du är på 9.5 och inte behöver vara bakåtkompatibel kan du sluta läsa nu .
9.4 och äldre:
PostgreSQL har ingen inbyggd UPSERT
(eller MERGE
) anläggning, och att göra det effektivt vid samtidig användning är mycket svårt.
Den här artikeln diskuterar problemet i användbar detalj.
I allmänhet måste du välja mellan två alternativ:
- Individuella infognings-/uppdateringsoperationer i en återförsöksslinga; eller
- Låsa bordet och slå samman batch
Enskild rad försök igen loop
Att använda individuella raduppstigningar i en återförsöksslinga är det rimliga alternativet om du vill att många anslutningar samtidigt försöker utföra infogning.
PostgreSQL-dokumentationen innehåller en användbar procedur som låter dig göra detta i en loop inuti databasen. Det skyddar mot förlorade uppdateringar och infoga raser, till skillnad från de flesta naiva lösningar. Det fungerar bara i READ COMMITTED
läge och är bara säkert om det är det enda du gör i transaktionen. Funktionen fungerar inte korrekt om triggers eller sekundära unika nycklar orsakar unika överträdelser.
Denna strategi är mycket ineffektiv. Närhelst det är praktiskt möjligt bör du köa arbetet och göra en bulk-upsert enligt beskrivningen nedan istället.
Många försök till lösningar på detta problem tar inte hänsyn till återställningar, så de resulterar i ofullständiga uppdateringar. Två transaktioner rasar med varandra; en av dem lyckades INSERT
s; den andra får ett duplicerat nyckelfel och gör en UPDATE
istället. UPDATE
block som väntar på INSERT
att återställa eller begå. När den rullar tillbaka visas UPDATE
villkorskontroll matchar noll rader, så även om UPDATE
begår att det faktiskt inte har gjort det upprörande du förväntade dig. Du måste kontrollera antalet resultatrader och försöka igen vid behov.
Vissa försök till lösningar tar inte heller hänsyn till SELECT-lopp. Om du försöker det självklara och enkla:
-- 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;
sedan när två körs samtidigt finns det flera fellägen. Det ena är det redan diskuterade problemet med en omkontroll av uppdateringen. En annan är där både UPDATE
samtidigt, matcha nollrader och fortsätta. Sedan gör de båda EXISTS
test, vilket händer före INSERT
. Båda får noll rader, så båda gör INSERT
. Man misslyckas med ett duplicerat nyckelfel.
Det är därför du behöver en slinga igen. Du kanske tror att du kan förhindra dubbletter av nyckelfel eller förlorade uppdateringar med smart SQL, men du kan inte. Du måste kontrollera radantal eller hantera dubbla nyckelfel (beroende på vald metod) och försöka igen.
Vänligen rulla inte din egen lösning för detta. Precis som med meddelandekö, är det förmodligen fel.
Uppkast med lås
Ibland vill man göra en bulk upsert, där man har en ny datamängd som man vill slå ihop till en äldre befintlig datamängd. Det här är väldigt effektivare än individuella raduppstigningar och bör föredras närhelst det är praktiskt möjligt.
I det här fallet följer du vanligtvis följande process:
-
CREATE
enTEMPORARY
bord -
COPY
eller bulkinfoga den nya datan i temptabellen -
LOCK
måltabellenIN EXCLUSIVE MODE
. Detta tillåter andra transaktioner attSELECT
, men gör inga ändringar i tabellen. -
Gör en
UPDATE ... FROM
av befintliga poster med värdena i temptabellen; -
Gör en
INSERT
rader som inte redan finns i måltabellen; -
COMMIT
, släpper låset.
Till exempel, för exemplet i frågan, med INSERT
med flera värden för att fylla i temptabellen:
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;
Relaterad läsning
- UPSERT wikisida
- UPSERTisms i Postgres
- Infoga, vid dubblettuppdatering i PostgreSQL?
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- Upptryckt med en transaktion
- Är SELECT eller INSERT i en funktion utsatt för tävlingsförhållanden?
- SQL
MERGE
på PostgreSQL-wikin - Det mest idiomatiska sättet att implementera UPSERT i Postgresql nuförtiden
Vad sägs om MERGE
?
SQL-standard MERGE
har faktiskt dåligt definierad samtidighetssemantik och är inte lämplig för upsertering utan att först låsa en tabell.
Det är ett riktigt användbart OLAP-uttalande för datasammanslagning, men det är faktiskt inte en användbar lösning för samtidighetssäker upsert. Det finns många råd till personer som använder andra DBMS:er för att använda MERGE
för upserts, men det är faktiskt fel.
Andra DB:er:
INSERT ... ON DUPLICATE KEY UPDATE
i MySQLMERGE
från MS SQL Server (men se ovan omMERGE
problem)MERGE
från Oracle (men se ovan omMERGE
problem)