sql >> Databasteknik >  >> RDS >> PostgreSQL

Förstå systemkolumner i PostgreSQL

Så du sitter med händerna över ett tangentbord och tänker "vad kul jag kan ha för att göra min livstid ännu mer nyfiken?..." Jo - skapa ett bord såklart!

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

Vad kul är det med en tabell utan data?.. Absolut ingen! Men jag kan enkelt fixa det:

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

Det ser konstigt och ganska dumt ut att ha en tabell utan kolumner och en rad. För att inte nämna att det inte är klart vilka "standardvärden" som infogades... Tja - att läsa några rader från dokument avslöjar att "Alla kolumner kommer att fyllas med sina standardvärden .” Ändå har jag inga kolumner! Tja - jag har säkert några:

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)

Så dessa sex är definitivt inte ALTER TABLE DROP COLUMN zombies eftersom attisdropped är falskt. Jag ser också att typnamnet för dessa kolumner slutar med "id". Att läsa den nedre delen av Objektidentifieringstyper kommer att ge idén. En annan rolig observation är - -2:an saknas! Jag undrar var jag kunde ha tappat det - jag skapade bara ett bord trots allt! Hm, vilken objektidentifierare saknas i min tabell? Per definition menar jag. Jag har tupel-, kommando- och xact-ID. Såvida inte någon "global över hela db-identifierare", som oid?.. Kontrollera är lätt - jag kommer att skapa tabell med 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! Så den saknade -2 saknas verkligen och vi gillar det. Att spendera oids för använda datarader skulle vara en dålig idé, så jag kommer att fortsätta spela med en tabell utan OIDS.

Vad jag har? Jag har 6 attribut efter att ha skapat "ingen kolumntabell" med (oids=false). Ska jag använda systemkolumner? Om så är fallet, varför är de dolda? Tja - jag skulle anta att de inte är så brett annonserade, eftersom användningen inte är intuitiv och beteende kan förändras i framtiden. Till exempel efter att ha sett tuple id (ctid) kanske vissa tänker "ah - det här är typ av intern PK" (och det är det typ):

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

Första siffran (noll) står för sidnumret och den andra (en) står för tupelnumret. De är sekventiella:

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

Men den här sekvensen hjälper dig inte att definiera ens vilken rad som kom efter vilken:

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)

Här lade jag till en kolumn (för att identifiera mina rader) och fyllde den med initialt tupelnummer (tänk på att båda raderna flyttades fysiskt)

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)

A ha! (sagt med stigande intonation) - här tog jag bort en av mina rader, släppte ut vakuumet på det stackars bordet och satte in en ny rad. Resultatet - den senare tillagda raden är i första sidans första tuppel, eftersom Postgres klokt beslutat att spara utrymme och återanvända det frigjorda utrymmet.

Så idén att använda ctid för att få sekvensen av rader införd ser dålig ut. Upp till någon nivå - om du arbetar i en transaktion kvarstår sekvensen - nypåverkade rader på samma bord kommer att ha "större" ctid. Naturligtvis kommer efter vakuum (autovakuum) eller om du har turen att ha HOT uppdateringar tidigare eller bara släppta luckor att återanvändas - vilket bryter den sekventiella ordningen. Men frukta inte - det fanns sex dolda attribut, inte en!

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

Om jag kontrollerar xmin kommer jag att se att transaktions-id:t som introducerade den senast infogade raden är (+2) högre (+1 var den raderade raden). Så för sekventiell radidentifierare kan jag använda ett helt annat attribut! Naturligtvis är det inte så enkelt, annars skulle sådan användning uppmuntras. Xmin-kolumnen före 9.4 skrevs faktiskt över för att skydda mot xid-omslutning. Varför så komplicerat? MVCC i Postgres är väldigt smart och metoderna kring det blir bättre med tiden. Naturligtvis medför det komplexitet. Ack. Vissa människor vill till och med undvika systemkolumner. Dubbla tyvärr. Eftersom systemkolumner är coola och väldokumenterade. Det allra bästa attributet (kom ihåg att jag hoppar över oids) är tableoid:

vao=# select i, tableoid from nocol;
 i | tableoid 
---+----------
   |   253952
 2 |   253952
(2 rows)
Ladda ner Whitepaper Today PostgreSQL Management &Automation med ClusterControlLäs om vad du behöver veta för att distribuera, övervaka, hantera och skala PostgreSQLDladda Whitepaper

Det ser värdelöst ut med SAMMA värde på varje rad – eller hur? Och ändå för ett tag sedan var det ett väldigt populärt attribut - när vi alla byggde partitionering med regler och ärvda tabeller. Hur skulle du felsöka vilken tabell raden kommer från om inte med tableoid? Så när du använder regler, vyer (samma regler) eller UNION hjälper tableoid-attributet dig att identifiera källan:

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 vad var det? Jag har vant mig så mycket vid att se INSERT 0 1 att min psql-utgång såg konstig ut! Ah - sant - jag skapade en tabell med oids och använde bara desperat meningslöst en (253967) identifierare! Tja - inte helt meningslöst (men desperat) - select returnerar två rader med samma ctid (0,1) - inte förvånande - jag väljer från två tabeller och lägger sedan till resultat till varandra, så chansen att ha samma ctid är inte så lågt. Det sista att nämna är att jag igen kan använda objektidentifieringstyper för att visa det snyggt:

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)

A ha! (sagt med stigande intonation) - Så det är sättet att tydligt fästa datakällan här!

Äntligen en annan mycket populär och intressant användning - som definierar vilken rad som infogades och vilken som lades upp:

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

Nu när vi har en PK kan jag använda ON CONFLICT-direktivet:

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)
Relaterade resurser ClusterControl for PostgreSQL Förstå och läsa PostgreSQL-systemkatalogen En översikt över databasindexering i PostgreSQL

Varför så glad? Eftersom jag kan säga (med viss konfidentialitet) att raden med xmax inte är lika med noll att den har uppdaterats. Och tro inte att det är uppenbart - det ser så ut bara för att jag använde unixtime för PK, så det ser verkligen annorlunda ut än ensiffriga värden. Föreställ dig att du gör en sådan ON CONFLICT twist på stora set och det finns inget logiskt sätt att identifiera vilket värde som hade konflikt och vilket - inte. xmax hjälpte ton DBA i svåra tider. Och den bästa beskrivningen av hur det fungerar skulle jag rekommendera här - precis som jag skulle rekommendera alla tre diskussionsdeltagarna (Abelisto, Erwin och Laurenz) att läsa på andra postgres-taggar frågor och svar på SO.

Det är det.

tableoid, xmax, xmin och ctid är goda vänner till alla DBA. Inte för att förolämpa cmax, cmin och oid - de är lika bra vänner också! Men det här räcker för en liten recension och jag vill ta bort händerna från tangentbordet nu.


  1. IN vs OR för Oracle, vilket snabbare?

  2. sql cross join - vilken användning har någon hittat för det?

  3. Hur man kör lagrad procedur från Laravel

  4. Hur kan jag lagra och hämta bilder från en MySQL-databas med PHP?