PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL Upsert differenzia le righe inserite e aggiornate utilizzando le colonne di sistema XMIN, XMAX e altre

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:

  1. 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 xmin e xmax può vedere la tupla. Una vecchia tupla può essere eliminata in modo sicuro se non ci sono transazioni con un ID transazione inferiore a xmax .

  2. xmax viene 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, xmax conterrà l'ID transazione della transazione di blocco. Se più di una transazione ha un blocco sulla riga, xmax contiene 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 .

  1. Al puntatore di linea (lp ) 1 troviamo la vecchia tupla aggiornata. Originariamente aveva ctid = (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 il INSERT ... ON CONFLICT ). Questa tupla non è più visibile e verrà rimossa durante VACUUM .

    t_infomask mostra che entrambi xmin e xmax appartengono a transazioni confermate e di conseguenza mostrano quando le tuple sono state create ed eliminate. t_infomask2 mostra 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 (vedi src/backend/access/heap/README.HOT ).

  2. Al puntatore di riga 2 vediamo la nuova tupla aggiornata creata dalla transazione INSERT ... ON CONFLICT (transazione 102508).

    t_infomask mostra che questa tupla è il risultato di un aggiornamento, xmin è valido e xmax contiene un KEY SHARE blocco riga (che non è più rilevante poiché la transazione è stata completata). Questo blocco riga è stato eseguito durante INSERT ... ON CONFLICT in lavorazione. t_infomask2 mostra che questa è una tupla HOT.

  3. Al puntatore di riga 3 vediamo la riga appena inserita.

    t_infomask mostra che xmin è valido e xmax è 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.