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:
-
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
ochxmax
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 änxmax
. -
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
.
-
Vid linjepekaren (
lp
) 1 vi hittar den gamla, uppdaterade tupeln. Den hade ursprungligenctid = (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ärdadeINSERT ... ON CONFLICT
). Denna tuppel är inte synlig längre och kommer att tas bort underVACUUM
.t_infomask
visar att bådexmin
ochxmax
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 (sesrc/backend/access/heap/README.HOT
). -
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 ochxmax
innehåller enKEY SHARE
radlås (vilket inte längre är aktuellt eftersom transaktionen har slutförts). Detta radlås togs underINSERT ... ON CONFLICT
bearbetning.t_infomask2
visar att detta är en HET tupel. -
Vid linjepekare 3 ser vi den nyinfogade raden.
t_infomask
visar attxmin
är giltig ochxmax
är ogiltig.xmax
är satt till 0 eftersom detta värde alltid används för nyligen infogade tupler.
Så 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.