PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

PostgreSQL Upsert rozróżnia wstawione i zaktualizowane wiersze za pomocą kolumn systemowych XMIN, XMAX i innych

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:

  1. 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 xmin i xmax widzi krotkę. Starą krotkę można bezpiecznie usunąć, jeśli nie ma transakcji o identyfikatorze transakcji mniejszym niż xmax .

  2. xmax sł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, xmax będzie zawierać identyfikator transakcji blokującej. Jeśli więcej niż jedna transakcja ma blokadę w wierszu, xmax zawiera 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 .

  1. 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ła INSERT ... ON CONFLICT ). Ta krotka nie jest już widoczna i zostanie usunięta podczas VACUUM .

    t_infomask pokazuje, że oba xmin i xmax należą do zatwierdzonych transakcji i w konsekwencji pokazują, kiedy krotki zostały utworzone i usunięte. t_infomask2 pokazuje, ż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 (zobacz src/backend/access/heap/README.HOT ).

  2. W wierszu wskaźnika 2 widzimy nową, zaktualizowaną krotkę utworzoną przez transakcję INSERT ... ON CONFLICT (transakcja 102508).

    t_infomask pokazuje, że ta krotka jest wynikiem aktualizacji, xmin jest prawidłowy, a xmax zawiera KEY SHARE blokada wiersza (która nie ma już znaczenia od czasu zakończenia transakcji). Ta blokada wiersza została zajęta podczas INSERT ... ON CONFLICT przetwarzanie. t_infomask2 pokazuje, że jest to GORĄCA krotka.

  3. Przy wskaźniku linii 3 widzimy nowo wstawiony wiersz.

    t_infomask pokazuje, że xmin jest prawidłowy i xmax jest nieważny. xmax jest 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.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zwrócić zapytanie z funkcji?

  2. Korzystanie z zestawu narzędzi pt-pg-summary Percona dla PostgreSQL

  3. Niezbędne monitorowanie PostgreSQL — część 3

  4. Wyłącz ostrzeżenie w sqlalchemy

  5. Jaka jest domyślna kolejność listy zwracanej przez wywołanie filtra Django?