sql >> Database teknologi >  >> RDS >> PostgreSQL

Forståelse af systemkolonner i PostgreSQL

Så du sidder med hænderne over et tastatur og tænker "hvor sjovt kan jeg have for at gøre mit liv endnu mere nysgerrig?.." Nå - lav selvfølgelig et bord!

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

Hvad sjovt er det ved en tabel uden data?.. Absolut ingen! Men jeg kan sagtens ordne det:

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

Det ser mærkeligt og ret dumt ud at have en tabel uden kolonner og én række. For ikke at nævne, at det ikke er klart, hvilke "standardværdier" der blev indsat... Nå - ved at læse nogle få linjer fra dokumenter afslører det, at "Alle kolonner vil blive udfyldt med deres standardværdier ." Alligevel har jeg ingen kolonner! Nå - jeg har helt sikkert nogle:

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å disse seks er bestemt ikke ALTER TABLE DROP COLUMN zombier, fordi attisdropped er falsk. Jeg kan også se, at typenavnet på disse kolonner ender med "id". At læse den nederste sektion af Objektidentifikatortyper vil give ideen. En anden sjov observation er - -2 mangler! Jeg spekulerer på, hvor jeg kunne have mistet det - jeg har jo lige lavet et bord! Hm, hvilken objektidentifikator mangler i min tabel? Per definition mener jeg. Jeg har tuple, kommando og xact id'er. Nå, medmindre en "global over hele db identifikator", som oid?.. Kontrol er let - jeg vil oprette tabel 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 manglende -2 mangler faktisk, og vi kan lide det. Det ville være en dårlig idé at bruge oids for brugte datarækker, så jeg bliver ved med at spille med en tabel uden OIDS.

Hvad har jeg? Jeg har 6 attributter efter at have oprettet "ingen kolonne tabel" med (oids=false). Skal jeg bruge systemkolonner? Hvis ja, hvorfor er de lidt skjulte? Nå - jeg vil antage, at de ikke er så bredt annonceret, fordi brugen ikke er intuitiv, og adfærd kan ændre sig i fremtiden. For eksempel efter at have set tuple id (ctid) vil nogle måske tænke "ah - det er en slags intern PK" (og det er det sådan set):

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

Det første cifre (nul) står for sidetallet og det andet (et) står for tupeltallet. De er sekventielle:

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

Men denne sekvens hjælper dig ikke med at definere, selv hvilken række der ankom, hvorefter:

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)

Her tilføjede jeg en kolonne (for at identificere mine rækker) og fyldte den med det første tupelnummer (husk, at begge rækker fysisk blev flyttet)

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! (sagt med stigende intonation) - her slettede jeg en af ​​mine rækker, lukkede vakuumet ud på det stakkels bord og indsatte en ny række. Resultatet - den senere tilføjede række er i den første sides første tuple, fordi Postgres klogt besluttede at spare plads og genbruge den frigjorte plads.

Så ideen om at bruge ctid til at få indført rækkefølgen af ​​rækker ser dårlig ud. Op til et eller andet niveau - hvis du arbejder i en transaktion, forbliver sekvensen - nye berørte rækker på samme tabel vil have "større" ctid. Selvfølgelig efter vakuum (autovakuum), eller hvis du er så heldig at have HOT-opdateringer tidligere eller blot frigivne huller, vil blive genbrugt - hvilket bryder den sekventielle rækkefølge. Men frygt ej – der var seks skjulte egenskaber, ikke én!

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

Hvis jeg tjekker xmin, vil jeg se, at transaktions-id'et, der introducerede den sidst indsatte række, er (+2) højere (+1 var den slettede række). Så til sekventiel rækkeidentifikator kan jeg bruge en helt anden attribut! Selvfølgelig er det ikke så enkelt, ellers ville sådan brug blive opmuntret. Xmin-kolonnen før 9.4 blev faktisk overskrevet for at beskytte mod xid-omvikling. Hvorfor så kompliceret? MVCC i Postgres er meget smart og metoder omkring det bliver bedre med tiden. Selvfølgelig medfører det kompleksitet. Ak. Nogle mennesker ønsker endda at undgå systemkolonner. Dobbelt desværre. Fordi systemkolonner er seje og veldokumenterede. Den allerøverste attribut (husk, at jeg springer oids over) er tableoid:

vao=# select i, tableoid from nocol;
 i | tableoid 
---+----------
   |   253952
 2 |   253952
(2 rows)
Download Whitepaper Today PostgreSQL Management &Automation med ClusterControlFå flere oplysninger om, hvad du skal vide for at implementere, overvåge, administrere og skalere PostgreSQLDownload Whitepaper

Det ser ubrugeligt ud med SAMME værdi i hver række - gør det ikke? Og alligevel for et stykke tid siden var det meget populær egenskab - da vi alle byggede partitionering ved hjælp af regler og nedarvede tabeller. Hvordan ville du fejlsøge hvilken tabel rækken kommer fra, hvis ikke med tableoid? Så når du bruger regler, visninger (samme regler) eller UNION hjælper tableoid-attributten dig med at identificere kilden:

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 hvad var det? Jeg har vænnet mig så meget til at se INSERT 0 1, at mit psql-output så mærkeligt ud! Ah - sandt - jeg oprettede en tabel med oids og brugte bare desperat meningsløst en (253967) identifikator! Nå - ikke helt meningsløst (dog desperat) - select returnerer to rækker med samme ctid (0,1) - ikke overraskende - jeg vælger fra to tabeller og tilføjer derefter resultater til hinanden, så chancen for at have den samme ctid er ikke så lav. Den sidste ting at nævne er, at jeg igen kan bruge objektidentifikatortyper til at vise det smukt:

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! (sagt med stigende intonation) - Så det er måden at fastgøre datakilden tydeligt her!

Endelig en anden meget populær og interessant brug - der definerer hvilken række der blev indsat og hvilken der blev opsat:

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 hvor vi har en PK, kan jeg bruge 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)
Relaterede ressourcer ClusterControl for PostgreSQL Forståelse og læsning af PostgreSQL-systemkataloget En oversigt over databaseindeksering i PostgreSQL

Hvorfor så glad? Fordi jeg kan fortælle (med en vis fortrolighed), at rækken med xmax ikke er lig med nul, at den blev opdateret. Og tro ikke, det er indlysende - det ser sådan ud, bare fordi jeg brugte unixtime til PK, så det ser virkelig anderledes ud end et-cifrede værdier. Forestil dig, at du laver sådan et ON CONFLICT twist på stort set, og der er ingen logisk måde at identificere, hvilken værdi der havde konflikt og hvilken - ikke. xmax hjalp tonsvis af DBA'er i hårde tider. Og den bedste beskrivelse af, hvordan det virker, vil jeg anbefale her - ligesom jeg vil anbefale alle tre diskussionsdeltagere (Abelisto, Erwin og Laurenz) at blive læst på andre postgres tag spørgsmål og svar på SO.

Det er det.

tableoid, xmax, xmin og ctid er gode venner af enhver DBA. Ikke for at fornærme cmax, cmin og oid - de er også lige gode venner! Men dette er nok til en lille anmeldelse, og jeg vil gerne have hænderne fra tastaturet nu.


  1. Kopier/dupliker database uden at bruge mysqldump

  2. PostgreSQL, SQL-tilstand:42601

  3. Reneste måde at bygge en SQL-streng i Java

  4. SQLite udtryksbaseret indeks