Ich denke, dass dies eine interessante Frage ist, die eine eingehende Antwort verdient; Bitte haben Sie Verständnis, wenn es etwas länger wird.
Kurz gesagt:Ihre Vermutung ist richtig, und Sie können den folgenden RETURNING
verwenden -Klausel, um festzustellen, ob die Zeile eingefügt und nicht aktualisiert wurde:
RETURNING (xmax = 0) AS inserted
Nun die ausführliche Erklärung:
Wenn eine Zeile aktualisiert wird, ändert PostgreSQL die Daten nicht, sondern erstellt eine neue Version der Reihe; die alte Version wird durch autovacuum gelöscht wenn es nicht mehr benötigt wird. Eine Version einer Zeile wird als Tupel bezeichnet , daher kann es in PostgreSQL mehr als ein Tupel pro Zeile geben.
xmax
dient zwei verschiedenen Zwecken:
-
Wie in der Dokumentation angegeben, kann es sich um die Transaktions-ID der Transaktion handeln, die das Tupel gelöscht (oder aktualisiert) hat („Tupel“ ist ein anderes Wort für „Zeile“). Nur Transaktionen mit einer Transaktions-ID zwischen
xmin
undxmax
kann das Tupel sehen. Ein altes Tupel kann sicher gelöscht werden, wenn es keine Transaktion mit einer Transaktions-ID kleiner alsxmax
gibt . -
xmax
wird auch zum Speichern von Zeilensperren verwendet . In PostgreSQL werden Zeilensperren nicht in der Sperrtabelle gespeichert, sondern im Tupel, um einen Überlauf der Sperrtabelle zu vermeiden.
Wenn nur eine Transaktion eine Sperre auf der Zeile hat,xmax
enthält die Transaktions-ID der Sperrtransaktion. Wenn mehr als eine Transaktion eine Sperre für die Zeile hat,xmax
enthält die Nummer eines sogenannten Multixakts , das ist eine Datenstruktur, die wiederum die Transaktions-IDs der sperrenden Transaktionen enthält.
Die Dokumentation von xmax
ist nicht vollständig, da die genaue Bedeutung dieses Felds als Implementierungsdetail betrachtet wird und ohne Kenntnis von t_infomask
nicht verstanden werden kann des Tupels, das nicht sofort über SQL sichtbar ist.
Sie können das Contrib-Modul pageinspect
installieren um dieses und andere Felder eines Tupels anzuzeigen.
Ich habe Ihr Beispiel ausgeführt und das sehe ich, wenn ich heap_page_items
verwende Funktion zur Detailprüfung (die Transaktions-ID-Nummern sind bei mir natürlich anders):
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)
Die Bedeutung von t_infomask
und t_infomask2
finden Sie in src/include/access/htup_details.h
. lp_off
ist der Offset der Tupeldaten in der Seite und t_ctid
ist die aktuelle Tupel-ID die aus der Seitenzahl und einer Tupelzahl innerhalb der Seite besteht. Da die Tabelle neu erstellt wurde, befinden sich alle Daten auf Seite 0.
Lassen Sie mich die drei Zeilen besprechen, die von heap_page_items
zurückgegeben werden .
-
Bei Zeilenzeiger (
lp
) 1 finden wir das alte, aktualisierte Tupel. Es hatte ursprünglichctid = (0,1)
, aber das wurde geändert, um die Tupel-ID der aktuellen Version während des Updates zu enthalten. Das Tupel wurde von Transaktion 102507 erstellt und von Transaktion 102508 ungültig gemacht (die Transaktion, dieINSERT ... ON CONFLICT
ausgegeben hat ). Dieses Tupel ist nicht mehr sichtbar und wird beimVACUUM
entfernt .t_infomask
zeigt, dass sowohlxmin
undxmax
gehören zu festgeschriebenen Transaktionen und zeigen folglich, wann die Tupel erstellt und gelöscht wurden.t_infomask2
zeigt, dass das Tupel mit einem HOT (heap only tuple) aktualisiert wurde ) aktualisieren, was bedeutet, dass sich das aktualisierte Tupel auf derselben Seite wie das ursprüngliche Tupel befindet und keine indizierte Spalte geändert wurde (siehesrc/backend/access/heap/README.HOT
). ). -
Bei Zeilenzeiger 2 sehen wir das neue, aktualisierte Tupel, das durch die Transaktion
INSERT ... ON CONFLICT
erstellt wurde (Transaktion 102508).t_infomask
zeigt, dass dieses Tupel das Ergebnis einer Aktualisierung ist,xmin
gültig ist, undxmax
enthält einenKEY SHARE
Zeilensperre (die nicht mehr relevant ist, da die Transaktion abgeschlossen ist). Diese Zeilensperre wurde währendINSERT ... ON CONFLICT
gesetzt wird bearbeitet.t_infomask2
zeigt, dass dies ein HOT-Tupel ist. -
Bei Zeilenzeiger 3 sehen wir die neu eingefügte Zeile.
t_infomask
zeigt, dassxmin
ist gültig undxmax
ist ungültig.xmax
auf 0 gesetzt, da dieser Wert immer für neu eingefügte Tupel verwendet wird.
Also das xmax
ungleich Null der aktualisierten Zeile ist ein Implementierungsartefakt, das durch eine Zeilensperre verursacht wird. Denkbar ist, dass INSERT ... ON CONFLICT
wird eines Tages neu implementiert, so dass sich dieses Verhalten ändert, aber ich halte das für unwahrscheinlich.