sql >> Databasteknik >  >> RDS >> PostgreSQL

PostgreSQL Upsert särskilj infogade och uppdaterade rader med hjälp av systemkolumnerna XMIN, XMAX och andra

Jag tycker att detta är en intressant fråga som förtjänar ett ingående svar; snälla stå ut med mig om den är lite lång.

Kort sagt:Din gissning är rätt, och du kan använda följande RETURNING sats för att avgöra om raden har infogats och inte uppdaterats:

RETURNING (xmax = 0) AS inserted

Nu den detaljerade förklaringen:

När en rad uppdateras ändrar PostgreSQL inte data, utan skapar en ny version av raden; den gamla versionen kommer att raderas av autovacuum när det inte längre behövs. En version av en rad kallas en tuppel , så i PostgreSQL kan det finnas mer än en tuppel per rad.

xmax tjänar två olika syften:

  1. Som anges i dokumentationen kan det vara transaktions-ID:t för transaktionen som raderade (eller uppdaterade) tuppeln (”tuppel” är ett annat ord för “rad”). Endast transaktioner med ett transaktions-ID mellan xmin och xmax kan se tupeln. En gammal tuppel kan tas bort på ett säkert sätt om det inte finns någon transaktion med ett transaktions-ID mindre än xmax .

  2. xmax används också för att lagra radlås . I PostgreSQL lagras inte radlås i låstabellen, utan i tupel för att undvika översvämning av låstabell.
    Om bara en transaktion har ett lås på raden, xmax kommer att innehålla transaktions-ID för låstransaktionen. Om mer än en transaktion har ett lås på raden, xmax innehåller numret på en så kallad multixakt , som är en datastruktur som i sin tur innehåller transaktions-ID:n för låstransaktionerna.

Dokumentationen för xmax är inte fullständig, eftersom den exakta innebörden av detta fält anses vara en implementeringsdetalj och inte kan förstås utan att känna till t_infomask av tupeln, som inte är omedelbart synlig via SQL.

Du kan installera bidragsmodulen pageinspect för att se detta och andra fält i en tupel.

Jag körde ditt exempel, och det här är vad jag ser när jag använder heap_page_items funktion för att undersöka detaljer (transaktions-ID-numren är naturligtvis olika i mitt fall):

SELECT *, ctid, xmin, xmax FROM t;

┌───┬────┬───────┬────────┬────────┐
│ i │ x  │ ctid  │  xmin  │  xmax  │
├───┼────┼───────┼────────┼────────┤
│ 1 │ 11 │ (0,2) │ 102508 │ 102508 │
│ 2 │ 22 │ (0,3) │ 102508 │      0 │
└───┴────┴───────┴────────┴────────┘
(2 rows)

SELECT lp, lp_off, t_xmin, t_xmax, t_ctid,
       to_hex(t_infomask) AS t_infomask, to_hex(t_infomask2) AS t_infomask2
FROM heap_page_items(get_raw_page('laurenz.t', 0));

┌────┬────────┬────────┬────────┬────────┬────────────┬─────────────┐
│ lp │ lp_off │ t_xmin │ t_xmax │ t_ctid │ t_infomask │ t_infomask2 │
├────┼────────┼────────┼────────┼────────┼────────────┼─────────────┤
│  1 │   8160 │ 102507 │ 102508 │ (0,2)  │ 500        │ 4002        │
│  2 │   8128 │ 102508 │ 102508 │ (0,2)  │ 2190       │ 8002        │
│  3 │   8096 │ 102508 │      0 │ (0,3)  │ 900        │ 2           │
└────┴────────┴────────┴────────┴────────┴────────────┴─────────────┘
(3 rows)

Betydelsen av t_infomask och t_infomask2 finns i src/include/access/htup_details.h . lp_off är förskjutningen av tupeldata på sidan och t_ctid är det aktuella tuppel-ID som består av sidnumret och ett tupelnummer inom sidan. Eftersom tabellen nyskapades finns all data på sidan 0.

Låt mig diskutera de tre raderna som returneras av heap_page_items .

  1. Vid linjepekaren (lp ) 1 vi hittar den gamla, uppdaterade tupeln. Den hade ursprungligen ctid = (0,1) , men det ändrades för att innehålla tupel-ID:t för den aktuella versionen under uppdateringen. Tuple skapades av transaktion 102507 och ogiltigförklarades av transaktion 102508 (transaktionen som utfärdade INSERT ... ON CONFLICT ). Denna tuppel är inte synlig längre och kommer att tas bort under VACUUM .

    t_infomask visar att både xmin och xmax tillhör begångna transaktioner och visar följaktligen när tuplarna skapades och raderades. t_infomask2 visar att tuppeln uppdaterades med en HOT (enbart heap-tupel ) update, vilket innebär att den uppdaterade tuppeln är på samma sida som den ursprungliga tuppeln och ingen indexerad kolumn har ändrats (se src/backend/access/heap/README.HOT ).

  2. Vid linjepekare 2 ser vi den nya, uppdaterade tuppeln som skapades av transaktionen INSERT ... ON CONFLICT (transaktion 102508).

    t_infomask visar att denna tuppel är resultatet av en uppdatering, xmin är giltigt och xmax innehåller en KEY SHARE radlås (vilket inte längre är aktuellt eftersom transaktionen har slutförts). Detta radlås togs under INSERT ... ON CONFLICT bearbetning. t_infomask2 visar att detta är en HET tupel.

  3. Vid linjepekare 3 ser vi den nyinfogade raden.

    t_infomask visar att xmin är giltig och xmax är ogiltig. xmax är satt till 0 eftersom detta värde alltid används för nyligen infogade tupler.

xmax som inte är noll av den uppdaterade raden är en implementeringsartefakt orsakad av ett radlås. Det är tänkbart att INSERT ... ON CONFLICT implementeras om en dag så att detta beteende förändras, men jag tror att det är osannolikt.




  1. Gör du dessa misstag när du använder SQL CURSOR?

  2. Vad är SQL Server-ekvivalenten för ELT() i MySQL?

  3. Hur ansluter jag till PostgreSQL utan att ange ett databasnamn?

  4. Använda MySQL Galera Cluster Replication för att skapa ett geodistribuerat kluster:Del två