Penso che questa sia una domanda interessante che meriti una risposta approfondita; per favore, abbi pazienza se è un po' lungo.
In breve:la tua ipotesi è giusta e puoi utilizzare il seguente RETURNING clausola per determinare se la riga è stata inserita e non aggiornata:
RETURNING (xmax = 0) AS inserted
Ora la spiegazione dettagliata:
Quando una riga viene aggiornata, PostgreSQL non modifica i dati, ma crea una nuova versione della fila; la vecchia versione verrà eliminata da autovacuum quando non serve più. Una versione di una riga è chiamata tupla , quindi in PostgreSQL possono esserci più tuple per riga.
xmax ha due scopi diversi:
-
Come indicato nella documentazione, può essere l'ID transazione della transazione che ha eliminato (o aggiornato) la tupla ("tupla" è un'altra parola per "riga"). Solo transazioni con un ID transazione tra
xminexmaxpuò vedere la tupla. Una vecchia tupla può essere eliminata in modo sicuro se non ci sono transazioni con un ID transazione inferiore axmax. -
xmaxviene utilizzato anche per memorizzare blocchi di riga . In PostgreSQL, i blocchi di riga non sono archiviati nella tabella di blocco, ma nella tupla per evitare l'overflow della tabella di blocco.
Se solo una transazione ha un blocco sulla riga,xmaxconterrà l'ID transazione della transazione di blocco. Se più di una transazione ha un blocco sulla riga,xmaxcontiene il numero di un cosiddetto multixact , che è una struttura di dati che a sua volta contiene gli ID transazione delle transazioni di blocco.
La documentazione di xmax non è completo, perché il significato esatto di questo campo è considerato un dettaglio di implementazione e non può essere compreso senza conoscere t_infomask della tupla, che non è immediatamente visibile tramite SQL.
Puoi installare il modulo di contributo pageinspect per visualizzare questo e altri campi di una tupla.
Ho eseguito il tuo esempio e questo è ciò che vedo quando utilizzo heap_page_items funzione per esaminare i dettagli (i numeri ID transazione sono ovviamente diversi nel mio caso):
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)
I significati di t_infomask e t_infomask2 può essere trovato in src/include/access/htup_details.h . lp_off è l'offset dei dati della tupla nella pagina e t_ctid è l'ID tupla corrente che consiste nel numero di pagina e un numero tupla all'interno della pagina. Poiché la tabella è stata appena creata, tutti i dati sono nella pagina 0.
Consentitemi di discutere le tre righe restituite da heap_page_items .
-
Al puntatore di linea (
lp) 1 troviamo la vecchia tupla aggiornata. Originariamente avevactid = (0,1), ma è stato modificato per contenere l'ID tupla della versione corrente durante l'aggiornamento. La Tuple è stata creata dalla transazione 102507 e invalidata dalla transazione 102508 (la transazione che ha emesso ilINSERT ... ON CONFLICT). Questa tupla non è più visibile e verrà rimossa duranteVACUUM.t_infomaskmostra che entrambixminexmaxappartengono a transazioni confermate e di conseguenza mostrano quando le tuple sono state create ed eliminate.t_infomask2mostra che la tupla è stata aggiornata con una HOT (tupla solo heap ) update, il che significa che la tupla aggiornata si trova nella stessa pagina della tupla originale e non è stata modificata alcuna colonna indicizzata (vedisrc/backend/access/heap/README.HOT). -
Al puntatore di riga 2 vediamo la nuova tupla aggiornata creata dalla transazione
INSERT ... ON CONFLICT(transazione 102508).t_infomaskmostra che questa tupla è il risultato di un aggiornamento,xminè valido exmaxcontiene unKEY SHAREblocco riga (che non è più rilevante poiché la transazione è stata completata). Questo blocco riga è stato eseguito duranteINSERT ... ON CONFLICTin lavorazione.t_infomask2mostra che questa è una tupla HOT. -
Al puntatore di riga 3 vediamo la riga appena inserita.
t_infomaskmostra chexminè valido exmaxè invalido.xmaxè impostato su 0 perché questo valore viene sempre utilizzato per le tuple appena inserite.
Quindi il diverso da zero xmax della riga aggiornata è un artefatto di implementazione causato da un blocco di riga. È ipotizzabile che INSERT ... ON CONFLICT viene reimplementato un giorno in modo che questo comportamento cambi, ma penso che sia improbabile.