sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur UPSERT (SLAGA, INFOGA ... PÅ DUBLIKAT UPPDATERING) i PostgreSQL?

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 en TEMPORARY bord

  • COPY eller bulkinfoga den nya datan i temptabellen

  • LOCK måltabellen IN EXCLUSIVE MODE . Detta tillåter andra transaktioner att SELECT , 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 MySQL
  • MERGE från MS SQL Server (men se ovan om MERGE problem)
  • MERGE från Oracle (men se ovan om MERGE problem)


  1. PostgreSQL vs MySQL, en jämförelse

  2. 5 sätt att kontrollera en kolumns datatyp i SQLite

  3. Modellering av produktvarianter

  4. Självgodhet leder till:Risk blir verklighet