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.