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

Comprensione delle colonne di sistema in PostgreSQL

Quindi ti siedi con le mani su una tastiera e pensi "che divertimento posso avere per rendere la mia vita ancora più curiosa?..." Beh, crea un tavolo ovviamente!

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

Che divertimento c'è in una tabella senza dati?... Assolutamente nessuno! Ma posso risolverlo facilmente:

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

Sembra strano e abbastanza stupido avere una tabella senza colonne e una riga. Per non parlare del fatto che non è chiaro quali "valori predefiniti" siano stati inseriti... Bene, la lettura di poche righe di documenti rivela che "Tutte le colonne verranno riempite con i loro valori predefiniti .” Eppure non ho colonne! Bene, ne ho sicuramente alcuni:

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)

Quindi questi sei non sono sicuramente gli zombi ALTER TABLE DROP COLUMN perché attisdroped è falso. Inoltre vedo che il nome del tipo di quelle colonne finisce con "id". La lettura della sezione inferiore di Tipi di identificatore di oggetto darà l'idea. Un'altra osservazione divertente è che manca il -2! Mi chiedo dove potrei averlo perso:dopotutto ho appena creato un tavolo! Hm, quale identificatore di oggetto manca nella mia tabella? Per definizione intendo. Ho tuple, command e xact id. Bene, a meno che un "identificatore globale su tutto il db", come oid?.. Il controllo è facile:creerò una tabella con 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)

Ecco! Quindi manca davvero il -2 mancante e ci piace. Spendere OID per le righe di dati usate sarebbe una cattiva idea, quindi continuerò a giocare con una tabella senza OIDS.

Ciò che ho? Ho 6 attributi dopo aver creato "nessuna tabella di colonna" con (oids=false). Devo usare le colonne di sistema? Se sì, perché sono un po' nascosti? Bene, suppongo che non siano pubblicizzati in modo così ampio, perché l'utilizzo non è intuitivo e il comportamento può cambiare in futuro. Ad esempio, dopo aver visto tuple id (ctid), alcuni potrebbero pensare "ah - questa è una specie di PK interna" (e in qualche modo lo è):

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

Le prime cifre (zero) rappresentano il numero di pagina e la seconda (uno) il numero della tupla. Sono sequenziali:

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

Ma questa sequenza non ti aiuterà nemmeno a definire quale riga è arrivata dopo di che:

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)

Qui ho aggiunto una colonna (per identificare le mie righe) e l'ho riempita con il numero di tupla iniziale (attenzione che entrambe le righe sono state spostate fisicamente)

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)

Ah! (detto con intonazione crescente) - qui ho cancellato una mia riga, fatto uscire il vuoto sul povero tavolo e inserito una nuova riga. Il risultato:la riga aggiunta in seguito è nella prima tupla della prima pagina, perché Postgres ha saggiamente deciso di salvare lo spazio e riutilizzare lo spazio liberato.

Quindi l'idea di usare ctid per ottenere la sequenza di righe introdotta non sembra buona. Fino a un certo livello - se lavori in una transazione, la sequenza rimane - le nuove righe interessate sulla stessa tabella avranno ctid "più grande". Ovviamente dopo il vuoto (autovacuum) o se sei abbastanza fortunato da avere aggiornamenti HOT prima o solo le lacune rilasciate verranno riutilizzate, interrompendo l'ordine sequenziale. Ma non temere:c'erano sei attributi nascosti, non uno!

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

Se controllo xmin, vedrò che l'ID transazione che ha introdotto l'ultima riga inserita è (+2) più alto (+1 era la riga eliminata). Quindi per l'identificatore di riga sequenziale potrei usare un attributo completamente diverso! Ovviamente non è così semplice, altrimenti tale utilizzo sarebbe incoraggiato. La colonna xmin prima della 9.4 è stata effettivamente sovrascritta per proteggere dal wraparound xid. Perché così complicato? L'MVCC in Postgres è molto intelligente e i metodi per aggirarlo migliorano nel tempo. Ovviamente porta complessità. Ahimè. Alcune persone vogliono anche evitare le colonne di sistema. Doppio ahimè. Perché le colonne di sistema sono interessanti e ben documentate. L'attributo più in alto (ricorda che salto oids) è tableoid:

vao=# select i, tableoid from nocol;
 i | tableoid 
---+----------
   |   253952
 2 |   253952
(2 rows)
Scarica il whitepaper oggi Gestione e automazione di PostgreSQL con ClusterControlScopri ciò che devi sapere per distribuire, monitorare, gestire e ridimensionare PostgreSQLScarica il whitepaper

Sembra inutile avere lo STESSO valore in ogni riga, non è vero? Eppure qualche tempo fa era un attributo molto popolare, quando tutti costruivamo il partizionamento usando regole e tabelle ereditate. Come eseguiresti il ​​debug da quale tabella proviene la riga se non con tableoid? Quindi, quando usi regole, viste (stesse regole) o UNION, l'attributo tableoid ti aiuta a identificare la fonte:

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 cos'era? Sono così abituato a vedere INSERT 0 1 che il mio output psql sembrava strano! Ah - vero - ho creato una tabella con oids e ho usato disperatamente inutilmente un identificatore (253967)! Bene - non del tutto inutilmente (anche se disperatamente) - select restituisce due righe con lo stesso ctid (0,1) - non sorprendente - sto selezionando da due tabelle e quindi aggiungo i risultati l'uno all'altro, quindi la possibilità di avere lo stesso ctid non è così basso. L'ultima cosa da menzionare è che posso usare di nuovo i tipi di identificatore di oggetto per mostrarlo in modo carino:

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)

Ah! (detto con intonazione crescente) - Quindi questo è il modo per bloccare chiaramente l'origine dati qui!

Infine un altro utilizzo molto popolare e interessante:definire quale riga è stata inserita e quale capovolta:

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

Ora che abbiamo un PK, posso usare la direttiva 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)
Risorse correlate ClusterControl per PostgreSQL Comprensione e lettura del catalogo di sistema PostgreSQL Una panoramica dell'indicizzazione del database in PostgreSQL

Perché così felice? Perché posso dire (con una certa riservatezza) a quella riga con xmax non uguale a zero che è stata aggiornata. E non pensare che sia ovvio:sembra così solo perché ho usato unixtime per PK, quindi sembra davvero diverso dai valori a una cifra. Immagina di fare una tale svolta ON CONFLICT su un grande set e non c'è un modo logico per identificare quale valore abbia avuto conflitto e quale - no. xmax ha aiutato tonnellate di DBA in tempi difficili. E la migliore descrizione di come funziona la consiglierei qui, proprio come consiglierei a tutti e tre i partecipanti alla discussione (Abelisto, Erwin e Laurenz) di leggere altre domande e risposte sui tag postgres su SO.

Questo è tutto.

tableoid, xmax, xmin e ctid sono buoni amici di qualsiasi DBA. Per non insultare cmax, cmin e oid - anche loro sono buoni amici! Ma questo è sufficiente per una piccola recensione e ora voglio togliere le mani dalla tastiera.