PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comprendre les colonnes système dans PostgreSQL

Donc, vous vous asseyez avec vos mains sur un clavier et pensez "comment je peux m'amuser pour rendre ma vie encore plus curieuse ?"

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

Qu'est-ce que c'est amusant d'avoir une table sans données ? Absolument aucune ! Mais je peux facilement y remédier :

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

Cela semble bizarre et assez stupide d'avoir un tableau sans colonnes et une seule ligne. Sans oublier qu'il n'est pas clair quelles "valeurs par défaut" ont été insérées… Eh bien - la lecture de quelques lignes de documents révèle que "Toutes les colonnes seront remplies avec leurs valeurs par défaut .” Pourtant je n'ai pas de colonnes ! Eh bien - j'en ai sûrement :

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)

Donc, ces six ne sont certainement pas les zombies ALTER TABLE DROP COLUMN car attisdropped est faux. Je vois aussi que le nom du type de ces colonnes se termine par "id". La lecture de la section inférieure des types d'identificateurs d'objets vous donnera une idée. Une autre observation amusante est - le -2 est manquant ! Je me demande où j'ai pu le perdre - je viens de créer un tableau après tout ! Hm, quel identifiant d'objet manque dans ma table ? Par définition je veux dire. J'ai des identifiants de tuple, de commande et de xact. Eh bien, à moins qu'un "identifiant global sur toute la base de données", comme oid ?.. La vérification est facile - je vais créer une table avec 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 ! Donc, le -2 manquant manque en effet et nous l'aimons. Dépenser des oids pour les lignes de données utilisées serait une mauvaise idée, donc je vais continuer à jouer avec une table sans OIDS.

Ce que j'ai? J'ai 6 attributs après avoir créé "no column table" avec (oids=false). Dois-je utiliser des colonnes système ? Si oui, pourquoi sont-ils en quelque sorte cachés? Eh bien - je suppose qu'ils ne sont pas si largement annoncés, car l'utilisation n'est pas intuitive et le comportement peut changer à l'avenir. Par exemple, après avoir vu l'identifiant de tuple (ctid), certains pourraient penser "ah - c'est une sorte de PK interne" (et c'est en quelque sorte le cas) :

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

Les premiers chiffres (zéro) représentent le numéro de page et le second (un) représente le numéro de tuple. Ils sont séquentiels :

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

Mais cette séquence ne vous aidera même pas à définir quelle ligne est arrivée après laquelle :

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)

Ici, j'ai ajouté une colonne (pour identifier mes lignes) et l'ai remplie avec le numéro de tuple initial (attention, les deux lignes ont été physiquement déplacées)

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 ! (dit avec une intonation montante) - ici j'ai supprimé une de mes rangées, laissé le vide sur la pauvre table et inséré une nouvelle rangée. Le résultat - la ligne ajoutée ultérieurement se trouve dans le premier tuple de la première page, car Postgres a sagement décidé d'économiser de l'espace et de réutiliser l'espace libéré.

Ainsi, l'idée d'utiliser ctid pour obtenir la séquence de lignes introduite semble mauvaise. Jusqu'à un certain niveau - si vous travaillez dans une transaction, la séquence reste - les lignes nouvellement affectées sur la même table auront un ctid "plus grand". Bien sûr, après le vide (autovacuum) ou si vous avez la chance d'avoir des mises à jour HOT plus tôt ou que vous venez de publier, les lacunes seront réutilisées - rompant l'ordre séquentiel. Mais n'ayez crainte, il y avait six attributs cachés, pas un !

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

Si je vérifie le xmin, je verrai que l'identifiant de transaction qui a introduit la dernière ligne insérée est (+2) supérieur (+1 était la ligne supprimée). Donc, pour l'identifiant de ligne séquentiel, je pourrais utiliser un attribut totalement différent ! Bien sûr, ce n'est pas aussi simple, sinon une telle utilisation serait encouragée. La colonne xmin avant 9.4 était en fait écrasée pour se protéger du bouclage xid. Pourquoi si compliqué ? Le MVCC dans Postgres est très intelligent et les méthodes qui l'entourent s'améliorent avec le temps. Bien sûr, cela apporte de la complexité. Hélas. Certaines personnes veulent même éviter les colonnes système. Double hélas. Parce que les colonnes système sont cool et bien documentées. L'attribut tout en haut (rappelez-vous que j'ignore les oids) est tableoid :

vao=# select i, tableoid from nocol;
 i | tableoid 
---+----------
   |   253952
 2 |   253952
(2 rows)
Téléchargez le livre blanc aujourd'hui PostgreSQL Management &Automation with ClusterControlDécouvrez ce que vous devez savoir pour déployer, surveiller, gérer et faire évoluer PostgreSQLTélécharger le livre blanc

Il semble inutile d'avoir la même valeur dans chaque ligne - n'est-ce pas ? Et pourtant, il y a quelque temps, c'était un attribut très populaire - lorsque nous construisions tous un partitionnement à l'aide de règles et de tables héritées. Comment débogueriez-vous la table d'où provient la ligne si ce n'est pas avec tableoid ? Ainsi, lorsque vous utilisez des règles, des vues (mêmes règles) ou UNION, l'attribut tableoid vous aide à identifier la source :

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)

Waouh c'était quoi ça ? J'ai tellement l'habitude de voir INSERT 0 1 que ma sortie psql avait l'air bizarre ! Ah - vrai - j'ai créé une table avec des oids et j'ai désespérément utilisé un identifiant (253967) ! Eh bien - pas complètement inutilement (bien que désespérément) - la sélection renvoie deux lignes avec le même ctid (0,1) - pas surprenant - je sélectionne dans deux tables, puis j'ajoute des résultats l'un à l'autre, donc la chance d'avoir le même ctid n'est pas si faible. La dernière chose à mentionner est que je peux à nouveau utiliser des types d'identifiant d'objet pour le montrer joli :

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 ! (dit avec une intonation montante) - C'est donc la façon d'épingler clairement la source de données ici !

Enfin une autre utilisation très populaire et intéressante - définir quelle ligne a été insérée et laquelle a été mise à jour :

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

Maintenant que nous avons un PK, je peux utiliser la directive 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)
Ressources associées ClusterControl pour PostgreSQL Comprendre et lire le catalogue système PostgreSQL Un aperçu de l'indexation des bases de données dans PostgreSQL

Pourquoi si heureux? Parce que je peux dire (avec une certaine confidentialité) cette ligne avec xmax non égal à zéro qu'elle a été mise à jour. Et ne pensez pas que c'est évident - ça a l'air juste parce que j'ai utilisé unixtime pour PK, donc ça a l'air vraiment différent des valeurs à un chiffre. Imaginez que vous faites une telle torsion ON CONFLICT sur un grand ensemble et qu'il n'y a aucun moyen logique d'identifier quelle valeur a un conflit et laquelle - pas. xmax a aidé des tonnes de DBA dans les moments difficiles. Et la meilleure description de son fonctionnement que je recommanderais ici - tout comme je recommanderais aux trois participants à la discussion (Abelisto, Erwin et Laurenz) d'être lus sur d'autres questions et réponses sur les balises postgres sur SO.

C'est tout.

tableoid, xmax, xmin et ctid sont de bons amis de n'importe quel DBA. Sans insulter cmax, cmin et oid - ils sont aussi de bons amis ! Mais cela suffit pour une petite révision et je veux maintenant lâcher le clavier.