PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

PostgreSQL Upsert unterscheidet zwischen eingefügten und aktualisierten Zeilen unter Verwendung der Systemspalten XMIN, XMAX und anderer

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:

  1. 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 und xmax kann das Tupel sehen. Ein altes Tupel kann sicher gelöscht werden, wenn es keine Transaktion mit einer Transaktions-ID kleiner als xmax gibt .

  2. 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 .

  1. Bei Zeilenzeiger (lp ) 1 finden wir das alte, aktualisierte Tupel. Es hatte ursprünglich ctid = (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, die INSERT ... ON CONFLICT ausgegeben hat ). Dieses Tupel ist nicht mehr sichtbar und wird beim VACUUM entfernt .

    t_infomask zeigt, dass sowohl xmin und xmax 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 (siehe src/backend/access/heap/README.HOT). ).

  2. 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, und xmax enthält einen KEY SHARE Zeilensperre (die nicht mehr relevant ist, da die Transaktion abgeschlossen ist). Diese Zeilensperre wurde während INSERT ... ON CONFLICT gesetzt wird bearbeitet. t_infomask2 zeigt, dass dies ein HOT-Tupel ist.

  3. Bei Zeilenzeiger 3 sehen wir die neu eingefügte Zeile.

    t_infomask zeigt, dass xmin ist gültig und xmax 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.