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

Zrozumienie kolumn systemowych w PostgreSQL

Siedzisz więc z rękami nad klawiaturą i myślisz:„Jaką frajdę mogę mieć, żeby moje życie było jeszcze ciekawsze?”. Cóż – oczywiście stwórz stół!

vao=# create table nocol();
CREATE TABLE
vao=# select * from nocol;
--
(0 rows)

Co za frajda z tabelą bez danych?... Absolutnie żaden! Ale mogę to łatwo naprawić:

vao=# insert into nocol default values;
INSERT 0 1

Wygląda to dziwnie i dość głupio, aby mieć tabelę bez kolumn i z jednym rzędem. Nie wspominając o tym, że nie jest jasne, jakie „wartości domyślne” zostały tam wstawione… Cóż – przeczytanie kilku linijek z dokumentów pokazuje, że „Wszystkie kolumny zostaną wypełnione ich wartościami domyślnymi ”. Ale nie mam kolumn! Cóż - na pewno mam:

vao=# select attname, attnum, atttypid::regtype, attisdropped::text from pg_attribute where attrelid = 'nocol'::regclass;
 attname  | attnum | atttypid | attisdropped 
----------+--------+----------+--------------
 tableoid |     -7 | oid      | false
 cmax     |     -6 | cid      | false
 xmax     |     -5 | xid      | false
 cmin     |     -4 | cid      | false
 xmin     |     -3 | xid      | false
 ctid     |     -1 | tid      | false
(6 rows)

Więc te sześć zdecydowanie nie jest zombie ALTER TABLE DROP COLUMN, ponieważ attisdropped jest fałszywe. Widzę też, że nazwa typu tych kolumn kończy się na „id”. Zapoznanie się z dolną sekcją typów identyfikatorów obiektów da pomysł. Inną zabawną obserwacją jest to, że brakuje -2! Zastanawiam się, gdzie mogłem to zgubić - w końcu właśnie stworzyłem stół! Hm, jakiego identyfikatora obiektu brakuje w mojej tabeli? Z definicji mam na myśli. Mam identyfikatory krotek, poleceń i xact. No chyba, że ​​jakiś „globalny identyfikator całej bazy danych”, jak oid?.. Sprawdzenie jest łatwe - stworzę tabelę z OIDS:

vao=# create table nocol_withoid() with oids;
CREATE TABLE
vao=# select attname, attnum, atttypid::regtype, attisdropped::text from pg_attribute where attrelid = 'nocol_withoid'::regclass;
 attname  | attnum | atttypid | attisdropped 
----------+--------+----------+--------------
 tableoid |     -7 | oid      | false
 cmax     |     -6 | cid      | false
 xmax     |     -5 | xid      | false
 cmin     |     -4 | cid      | false
 xmin     |     -3 | xid      | false
 oid      |     -2 | oid      | false
 ctid     |     -1 | tid      | false
(7 rows)

Voila! Tak więc brakuje brakującego -2 i to nam się podoba. Wydawanie oidów na używane wiersze danych byłoby złym pomysłem, więc będę dalej bawić się tabelą bez OIDS.

Co ja mam? Mam 6 atrybutów po utworzeniu „tabeli bez kolumn” za pomocą (oids=false). Czy powinienem używać kolumn systemowych? Jeśli tak, dlaczego są ukryte? No cóż – zakładam, że nie są tak szeroko reklamowane, ponieważ obsługa nie jest intuicyjna i zachowanie może się zmienić w przyszłości. Na przykład po obejrzeniu identyfikatora krotki (ctid) niektórzy mogą pomyśleć „ach - to jest rodzaj wewnętrznego PK” (i tak jest):

vao=# select ctid from nocol;
 ctid  
-------
 (0,1)
(1 row)

Pierwsze cyfry (zero) oznaczają numer strony, a druga (jeden) numer krotki. Są sekwencyjne:

vao=# insert into nocol default values;
INSERT 0 1
vao=# select ctid from nocol;
 ctid  
-------
 (0,1)
 (0,2)
(2 rows)

Ale ta sekwencja nie pomoże ci określić nawet, który wiersz się pojawił, po którym:

vao=# alter table nocol add column i int;
ALTER TABLE
vao=# update nocol set i = substring(ctid::text from 4 for 1)::int;
UPDATE 2
vao=# select i, ctid from nocol;
 i | ctid  
---+-------
 1 | (0,3)
 2 | (0,4)
(2 rows)

Tutaj dodałem kolumnę (aby zidentyfikować moje wiersze) i wypełniłem ją początkowym numerem krotki (pamiętaj, że oba wiersze zostały fizycznie przeniesione)

vao=# delete from nocol where ctid = '(0,3)';
DELETE 1
vao=# vacuum nocol;
VACUUM
vao=# insert into nocol default values;
INSERT 0 1
vao=# select i, ctid from nocol;
 i | ctid  
---+-------
   | (0,1)
 2 | (0,4)
(2 rows)

Aha! (powiedział z rosnącą intonacją) - tutaj usunąłem jeden z moich wierszy, wypuściłem próżnię na biedny stół i wstawiłem nowy wiersz. Wynik - później dodany wiersz znajduje się w pierwszej krotce na pierwszej stronie, ponieważ Postgres mądrze zdecydował się zaoszczędzić miejsce i ponownie wykorzystać zwolnione miejsce.

Więc pomysł użycia ctid do uzyskania sekwencji wprowadzonych wierszy wygląda źle. Do pewnego poziomu - jeśli pracujesz w jednej transakcji, sekwencja pozostaje - nowe wiersze w tej samej tabeli będą miały „większy” ctid. Oczywiście po próżni (autovacuum) lub jeśli masz szczęście mieć aktualizacje HOT wcześniej lub właśnie wydane luki zostaną ponownie wykorzystane - łamiąc kolejność sekwencyjną. Ale nie bój się – było sześć ukrytych atrybutów, a nie jeden!

vao=# select i, ctid, xmin from nocol;
 i | ctid  | xmin  
---+-------+-------
   | (0,1) | 26211
 2 | (0,4) | 26209
(2 rows)

Jeśli sprawdzę xmin, zobaczę, że identyfikator transakcji, która wprowadziła ostatni wstawiony wiersz, jest (+2) wyższy (+1 to usunięty wiersz). Więc dla sekwencyjnego identyfikatora wiersza mogę użyć zupełnie innego atrybutu! Oczywiście nie jest to takie proste, w przeciwnym razie takie użycie byłoby zachęcane. Kolumna xmin przed wersją 9.4 została faktycznie nadpisana, aby chronić przed zawijaniem xid. Dlaczego tak skomplikowane? MVCC w Postgresie jest bardzo inteligentny, a metody wokół niego z czasem stają się coraz lepsze. Oczywiście przynosi to złożoność. Niestety. Niektórzy ludzie chcą nawet uniknąć kolumn systemowych. Niestety podwójnie. Ponieważ kolumny systemowe są fajne i dobrze udokumentowane. Najwyższym atrybutem (pamiętaj, że pomijam oidy) jest tableoid:

vao=# select i, tableoid from nocol;
 i | tableoid 
---+----------
   |   253952
 2 |   253952
(2 rows)
Pobierz oficjalny dokument już dziś Zarządzanie i automatyzacja PostgreSQL za pomocą ClusterControlDowiedz się, co musisz wiedzieć, aby wdrażać, monitorować, zarządzać i skalować PostgreSQLPobierz oficjalny dokument

Wygląda na bezużyteczne, mając TĄ SAMĄ wartość w każdym rzędzie - prawda? A jeszcze jakiś czas temu był to bardzo popularny atrybut - kiedy wszyscy budowaliśmy partycjonowanie przy użyciu reguł i dziedziczonych tabel. Jak debugowałbyś, z której tabeli pochodzi wiersz, jeśli nie z tableoid? Więc kiedy używasz reguł, widoków (tych samych reguł) lub UNION, atrybut tableoid pomaga zidentyfikować źródło:

vao=# insert into nocol_withoid default values;
INSERT 253967 1
vao=# select ctid, tableoid from nocol union select ctid, tableoid from nocol_withoid ;
 ctid  | tableoid 
-------+----------
 (0,1) |   253952
 (0,1) |   253961
 (0,4) |   253952
(3 rows)

Wow, co to było? Tak bardzo przyzwyczaiłem się do INSERT 0 1, że moje dane wyjściowe psql wyglądały dziwnie! Ach - prawda - stworzyłem tabelę z oidami i po prostu bezsensownie użyłem jednego (253967) identyfikatora! No cóż - nie do końca bez sensu (choć desperacko) - select zwraca dwa wiersze z tym samym ctid (0,1) - nic dziwnego - wybieram z dwóch tabel, a potem dodaję wyniki jedna do drugiej, więc szansa na ten sam ctid nie jest tak niski. Ostatnią rzeczą, o której należy wspomnieć, jest to, że mogę ponownie użyć typów identyfikatorów obiektów, aby pokazać to ładnie:

vao=# select ctid, tableoid::regclass from nocol union select ctid, tableoid from nocol_withoid ;
 ctid  |   tableoid    
-------+---------------
 (0,1) | nocol
 (0,1) | nocol_withoid
 (0,4) | nocol
(3 rows)

Aha! (powiedziane z rosnącą intonacją) - To jest sposób, aby wyraźnie przypiąć tutaj źródło danych!

Wreszcie kolejne bardzo popularne i interesujące użycie - określenie, który wiersz został wstawiony, a który przesunięty:

vao=# update nocol set i = 0 where i is null;
UPDATE 1
vao=# alter table nocol alter COLUMN i set not null;
ALTER TABLE
vao=# alter table nocol add constraint pk primary key (i);
ALTER TABLE

Teraz, gdy mamy PK, mogę użyć dyrektywy ON CONFLICT:

vao=# insert into nocol values(0),(-1) on conflict(i) do update set i = extract(epoch from now()) returning i, xmax;
     i      |   xmax    
------------+-----------
 1534433974 |     26281
         -1 |         0
(2 rows)
Powiązane zasoby ClusterControl for PostgreSQL Zrozumienie i odczytanie katalogu systemu PostgreSQL Przegląd indeksowania baz danych w PostgreSQL

Dlaczego tak szczęśliwy? Ponieważ mogę powiedzieć (z pewną poufnością), że wiersz z xmax nie równym zero, że został zaktualizowany. I nie myśl, że to oczywiste - wygląda tak tylko dlatego, że użyłem unixtime dla PK, więc wygląda naprawdę inaczej niż wartości jednocyfrowe. Wyobraź sobie, że robisz taki zwrot ON CONFLICT na dużym zestawie i nie ma logicznego sposobu, aby określić, która wartość była w konflikcie, a która - nie. xmax pomógł wielu DBA w trudnych czasach. A najlepszy opis tego, jak to działa, polecam tutaj - tak jak poleciłbym wszystkim trzem uczestnikom dyskusji (Abelisto, Erwin i Laurenz) przeczytanie innych pytań i odpowiedzi dotyczących tagów postgres na SO.

To wszystko.

tableoid, xmax, xmin i ctid są dobrymi przyjaciółmi każdego DBA. Nie obrażać cmax, cmin i oid - są równie dobrymi przyjaciółmi! Ale to wystarczy na małą recenzję i chcę teraz zdjąć ręce z klawiatury.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. jak wyświetlić pełny kod procedury składowanej?

  2. Jak stworzyć tymczasową funkcję w PostgreSQL?

  3. Django:Jakie są najlepsze praktyki migracji projektu z sqlite do PostgreSQL?

  4. Jak utworzyć użytkownika z uprawnieniami superużytkownika w PostgreSQL?

  5. Oblicz Max of Sum pola z adnotacjami w pogrupowanym zapytaniu w Django ORM?