Myślę, że to ciekawe pytanie, które zasługuje na dogłębną odpowiedź; proszę o wyrozumiałość, jeśli jest trochę za długi.
W skrócie:Twoje przypuszczenie jest prawidłowe i możesz użyć następującego RETURNING klauzula określająca, czy wiersz został wstawiony, a nie zaktualizowany:
RETURNING (xmax = 0) AS inserted
Teraz szczegółowe wyjaśnienie:
Gdy wiersz jest aktualizowany, PostgreSQL nie modyfikuje danych, ale tworzy nową wersję rzędu; stara wersja zostanie usunięta przez autovacuum kiedy nie jest już potrzebny. Wersja wiersza nazywana jest krotką , więc w PostgreSQL może być więcej niż jedna krotka w wierszu.
xmax służy dwóm różnym celom:
-
Jak stwierdzono w dokumentacji, może to być identyfikator transakcji, która usunęła (lub zaktualizowała) krotkę („krotka” to inne słowo oznaczające „wiersz”). Tylko transakcje z identyfikatorem transakcji między
xminixmaxwidzi krotkę. Starą krotkę można bezpiecznie usunąć, jeśli nie ma transakcji o identyfikatorze transakcji mniejszym niżxmax. -
xmaxsłuży również do przechowywania blokad wierszy . W PostgreSQL blokady wierszy nie są przechowywane w tabeli blokad, ale w krotce, aby uniknąć przepełnienia tabeli blokad.
Jeśli tylko jedna transakcja ma blokadę w wierszu,xmaxbędzie zawierać identyfikator transakcji blokującej. Jeśli więcej niż jedna transakcja ma blokadę w wierszu,xmaxzawiera numer tak zwanego multixact , która jest strukturą danych, która z kolei zawiera identyfikatory transakcji blokujących.
Dokumentacja xmax nie jest kompletne, ponieważ dokładne znaczenie tego pola jest uważane za szczegół implementacji i nie można go zrozumieć bez znajomości t_infomask krotki, która nie jest od razu widoczna przez SQL.
Możesz zainstalować moduł contrib pageinspect aby zobaczyć to i inne pola krotki.
Uruchomiłem twój przykład i to właśnie widzę, gdy używam heap_page_items funkcja do zbadania szczegółów (numery ID transakcji są oczywiście inne w moim przypadku):
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)
Znaczenia t_infomask i t_infomask2 można znaleźć w src/include/access/htup_details.h . lp_off jest przesunięciem danych krotki na stronie, a t_ctid to aktualny identyfikator krotki który składa się z numeru strony i numeru krotki na stronie. Ponieważ tabela została nowo utworzona, wszystkie dane znajdują się na stronie 0.
Pozwól, że omówię trzy wiersze zwrócone przez heap_page_items .
-
W wskaźniku linii (
lp) 1 znajdujemy starą, zaktualizowaną krotkę. Pierwotnie miałctid = (0,1), ale został zmodyfikowany tak, aby zawierał identyfikator krotki bieżącej wersji podczas aktualizacji. Krotka została utworzona przez transakcję 102507 i unieważniona przez transakcję 102508 (transakcja, która wysłałaINSERT ... ON CONFLICT). Ta krotka nie jest już widoczna i zostanie usunięta podczasVACUUM.t_infomaskpokazuje, że obaxminixmaxnależą do zatwierdzonych transakcji i w konsekwencji pokazują, kiedy krotki zostały utworzone i usunięte.t_infomask2pokazuje, że krotka została zaktualizowana za pomocą HOT (tylko krotka sterty ) update, co oznacza, że zaktualizowana krotka znajduje się na tej samej stronie co oryginalna krotka i żadna indeksowana kolumna nie została zmodyfikowana (zobaczsrc/backend/access/heap/README.HOT). -
W wierszu wskaźnika 2 widzimy nową, zaktualizowaną krotkę utworzoną przez transakcję
INSERT ... ON CONFLICT(transakcja 102508).t_infomaskpokazuje, że ta krotka jest wynikiem aktualizacji,xminjest prawidłowy, axmaxzawieraKEY SHAREblokada wiersza (która nie ma już znaczenia od czasu zakończenia transakcji). Ta blokada wiersza została zajęta podczasINSERT ... ON CONFLICTprzetwarzanie.t_infomask2pokazuje, że jest to GORĄCA krotka. -
Przy wskaźniku linii 3 widzimy nowo wstawiony wiersz.
t_infomaskpokazuje, żexminjest prawidłowy ixmaxjest nieważny.xmaxjest ustawiona na 0, ponieważ ta wartość jest zawsze używana dla nowo wstawianych krotek.
Więc niezerowy xmax zaktualizowanego wiersza to artefakt implementacji spowodowany blokadą wiersza. Można sobie wyobrazić, że INSERT ... ON CONFLICT zostanie ponownie wdrożony pewnego dnia, aby to zachowanie się zmieniło, ale myślę, że jest to mało prawdopodobne.