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
xminochxmaxkan 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. -
xmaxanvä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,xmaxkommer att innehålla transaktions-ID för låstransaktionen. Om mer än en transaktion har ett lås på raden,xmaxinnehå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_infomaskvisar att bådexminochxmaxtillhör begångna transaktioner och visar följaktligen när tuplarna skapades och raderades.t_infomask2visar 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_infomaskvisar att denna tuppel är resultatet av en uppdatering,xminär giltigt ochxmaxinnehåller enKEY SHAREradlås (vilket inte längre är aktuellt eftersom transaktionen har slutförts). Detta radlås togs underINSERT ... ON CONFLICTbearbetning.t_infomask2visar att detta är en HET tupel. -
Vid linjepekare 3 ser vi den nyinfogade raden.
t_infomaskvisar 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.