Така че седите с ръце над клавиатура и си мислите „какво забавление мога да се забавлявам, за да направя живота си още по-любопитен?..” Е – създайте маса, разбира се!
vao=# create table nocol();
CREATE TABLE
vao=# select * from nocol;
--
(0 rows)
Какво забавно е с таблица без данни?.. Абсолютно никакви! Но мога лесно да го поправя:
vao=# insert into nocol default values;
INSERT 0 1
Изглежда странно и доста глупаво да имаш таблица без колони и един ред. Да не говорим, че не е ясно какви „стойности по подразбиране“ са били вмъкнати… Е – четенето на няколко реда от документи разкрива, че „Всички колони ще бъдат запълнени със стойностите им по подразбиране ” И все пак нямам колони! Е - със сигурност имам някои:
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)
Така че тези шест определено не са зомбита от ALTER TABLE DROP COLUMN, защото attisdropped е фалшиво. Също така виждам, че името на типа на тези колони завършва с „id“. Четенето на долния раздел на Типовете идентификатори на обекта ще даде идеята. Друго смешно наблюдение е - липсва -2! Чудя се къде бих могъл да го загубя - все пак току-що създадох таблица! Хм, какъв идентификатор на обект липсва в моята таблица? По дефиниция имам предвид. Имам tuple, command и xact идентификатори. Е, освен ако някакъв "глобален идентификатор за цял db", като oid?.. Проверката е лесна - ще създам таблица с 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)
Вуаля! Така че липсващото -2 наистина липсва и ни харесва. Разходването на oids за използвани редове с данни би било лоша идея, така че ще продължа да играя с таблица без OIDS.
какво имам? Имам 6 атрибута, след като създадох „таблица без колони“ с (oids=false). Трябва ли да използвам системни колони? Ако е така, защо са някак скрити? Е - предполагам, че не са толкова широко рекламирани, защото употребата не е интуитивна и поведението може да се промени в бъдеще. Например, след като видят идентификатор на кортежа (ctid), някои може да си помислят „ах – това е нещо като вътрешен PK“ (и някак е):
vao=# select ctid from nocol;
ctid
-------
(0,1)
(1 row)
Първите цифри (нула) означават номера на страницата, а втората (единицата) означават номера на кортежа. Те са последователни:
vao=# insert into nocol default values;
INSERT 0 1
vao=# select ctid from nocol;
ctid
-------
(0,1)
(0,2)
(2 rows)
Но тази последователност няма да ви помогне да определите дори кой ред е пристигнал след кой:
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)
Тук добавих колона (за да идентифицирам моите редове) и я попълних с първоначален номер на кортежа (имайте предвид, че и двата реда бяха физически преместени)
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)
Аха! (казано с нарастваща интонация) - тук изтрих един от редовете си, пуснах вакуума на бедната маса и вмъкнах нов ред. Резултатът - добавеният по-късно ред е в първата страница на кортежа, тъй като Postgres разумно реши да спести място и да използва повторно освободеното място.
Така че идеята да се използва ctid за получаване на въведената последователност от редове изглежда лоша. До някакво ниво - ако работите в една транзакция, последователността остава - новозасегнатите редове в същата таблица ще имат "по-голям" ctid. Разбира се, след вакуумиране (автовакуум) или ако имате достатъчно късмет да имате ГОРЕЩИ актуализации по-рано или току-що освободените пропуски ще бъдат използвани повторно - нарушаване на последователния ред. Но не се страхувайте – имаше шест скрити атрибута, а не един!
vao=# select i, ctid, xmin from nocol;
i | ctid | xmin
---+-------+-------
| (0,1) | 26211
2 | (0,4) | 26209
(2 rows)
Ако проверя xmin, ще видя, че идентификаторът на транзакцията, който въведе последния вмъкнат ред, е (+2) по-висок (+1 беше изтритият ред). Така че за последователен идентификатор на ред може да използвам напълно различен атрибут! Разбира се, не е толкова просто, в противен случай подобна употреба би била насърчена. Колоната xmin преди 9.4 всъщност беше презаписана, за да се предпази от обвиване на xid. Защо толкова сложно? MVCC в Postgres е много интелигентен и методите около него се подобряват с времето. Разбира се, това носи сложност. уви. Някои хора дори искат да избегнат системните колони. Двойно уви. Тъй като системните колони са готини и добре документирани. Най-горният атрибут (не забравяйте, че пропускам oids) е tableoid:
vao=# select i, tableoid from nocol;
i | tableoid
---+----------
| 253952
2 | 253952
(2 rows)
Изтеглете Бялата книга днес Управление и автоматизация на PostgreSQL с ClusterControl Научете какво трябва да знаете, за да внедрите, наблюдавате, управлявате и мащабирате PostgreSQLD Изтеглете Бялата книга Изглежда безполезно да има ЕДНАВА стойност във всеки ред - нали? И все пак преди малко това беше много популярен атрибут - когато всички изграждахме разделяне, използвайки правила и наследени таблици. Как бихте отстранили грешките от коя таблица идва редът, ако не с tableoid? Така че, когато използвате правила, изгледи (същите правила) или UNION, атрибутът tableoid ви помага да идентифицирате източника:
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)
Леле какво беше това? Толкова много свикнах да виждам INSERT 0 1, че изходът ми в psql изглеждаше странен! Ах - вярно - създадох таблица с oids и просто отчаяно безсмислено използвах един (253967) идентификатор! Е - не напълно безсмислено (макар и отчаяно) - избраният връща два реда със същия ctid (0,1) - не е изненадващо - избирам от две таблици и след това добавям резултатите един към друг, така че шансът да имам същия ctid не е толкова ниско. Последното нещо, което трябва да спомена е, че мога отново да използвам типове идентификатори на обект, за да го покажа красиво:
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)
Аха! (казано с нарастваща интонация) - Така че това е начинът ясно да фиксирате източника на данни тук!
И накрая, още една много популярна и интересна употреба - дефиниране на кой ред е бил вмъкнат и кой е поставен:
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
Сега, когато имаме PK, мога да използвам директива 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)
Свързани ресурси ClusterControl за PostgreSQL Разбиране и четене на системния каталог на PostgreSQL Преглед на индексирането на база данни в PostgreSQL Защо толкова щастлив? Защото мога да кажа (с известна конфиденциалност), че редът с xmax не е равен на нула, че е актуализиран. И не мислете, че е очевидно - изглежда така, само защото използвах unixtime за PK, така че изглежда наистина различно от едноцифрените стойности. Представете си, че правите такъв ON CONFLICT обрат на голям набор и няма логичен начин да определите коя стойност е имала конфликт и коя - не. xmax помогна на тонове DBA в трудни времена. И най-доброто описание на начина, по който работи, бих препоръчал тук – точно както бих препоръчал и тримата участници в дискусията (Абелисто, Ервин и Лоренц) да бъдат прочетени за други въпроси и отговори на маркера на postgres в SO.
Това е.
tableoid, xmax, xmin и ctid са добри приятели на всеки DBA. Да не обиждам cmax, cmin и oid - те също са също толкова добри приятели! Но това е достатъчно за малък преглед и сега искам да махна ръцете си от клавиатурата.