PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Grundlegendes zu Systemspalten in PostgreSQL

Sie sitzen also mit Ihren Händen über einer Tastatur und denken:„Was für einen Spaß kann ich haben, um mein Leben noch neugieriger zu machen? …“ Na ja – erstellen Sie natürlich einen Tisch!

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

Welchen Spaß macht eine Tabelle ohne Daten? Absolut keine! Aber ich kann es leicht beheben:

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

Es sieht seltsam und ziemlich dumm aus, eine Tabelle ohne Spalten und mit einer Zeile zu haben. Ganz zu schweigen davon, dass nicht klar ist, welche „Standardwerte“ dort eingefügt wurden … Nun – das Lesen einiger Zeilen aus der Dokumentation zeigt, dass „Alle Spalten mit ihren Standardwerten gefüllt werden .“ Doch ich habe keine Spalten! Nun - ich habe sicher welche:

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)

Diese sechs sind also definitiv nicht die ALTER TABLE DROP COLUMN-Zombies, weil attisdropped falsch ist. Ich sehe auch, dass der Typname dieser Spalten mit „id“ endet. Lesen Sie den unteren Abschnitt von Object Identifier Types, um eine Idee zu bekommen. Eine weitere lustige Beobachtung ist - die -2 fehlt! Ich frage mich, wo ich es verloren haben könnte - ich habe doch gerade eine Tabelle erstellt! Hm, welche Objektkennung fehlt in meiner Tabelle? Definitionsgemäß meine ich. Ich habe Tupel-, Befehls- und Xact-IDs. Nun, es sei denn, eine „globale über ganze DB-Kennung“, wie oid? .. Die Überprüfung ist einfach - ich werde eine Tabelle mit OIDS erstellen:

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! Das fehlende -2 fehlt also tatsächlich und wir mögen es. Es wäre keine gute Idee, OIDs für verwendete Datenzeilen auszugeben, also spiele ich weiter mit einer Tabelle ohne OIDs.

Was ich habe? Ich habe 6 Attribute, nachdem ich „keine Spaltentabelle“ mit (oids=false) erstellt habe. Soll ich Systemspalten verwenden? Wenn ja, warum sind sie irgendwie versteckt? Gut - ich würde vermuten, dass sie nicht so breit beworben werden, weil die Nutzung nicht intuitiv ist und sich das Verhalten in Zukunft ändern kann. Zum Beispiel könnten einige nach dem Ansehen der Tupel-ID (ctid) denken „ah – das ist eine Art interner PK“ (und das ist sie auch):

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

Die erste Ziffer (Null) steht für die Seitennummer und die zweite (Eins) für die Tupelnummer. Sie sind sequentiell:

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

Aber diese Sequenz hilft Ihnen nicht einmal dabei, zu definieren, welche Zeile nach welcher kam:

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)

Hier habe ich eine Spalte hinzugefügt (um meine Zeilen zu identifizieren) und sie mit der anfänglichen Tupelnummer gefüllt (wobei beide Zeilen physisch verschoben wurden)

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! (mit steigender Intonation gesagt) - hier habe ich eine meiner Zeilen gelöscht, das Vakuum auf dem armen Tisch abgelassen und eine neue Zeile eingefügt. Das Ergebnis – die später hinzugefügte Zeile befindet sich im ersten Tupel der ersten Seite, weil Postgres sich klugerweise entschieden hat, den Platz zu sparen und den frei gewordenen Platz wiederzuverwenden.

Die Idee, ctid zu verwenden, um die Sequenz der eingeführten Zeilen zu erhalten, sieht also schlecht aus. Bis zu einer bestimmten Ebene – wenn Sie in einer Transaktion arbeiten, bleibt die Reihenfolge erhalten – haben neu betroffene Zeilen in derselben Tabelle eine „größere“ ctid. Natürlich werden nach dem Vakuum (Autovakuum) oder wenn Sie das Glück haben, früher HOT-Updates zu haben, oder gerade veröffentlichte Lücken wiederverwendet - wodurch die sequenzielle Reihenfolge unterbrochen wird. Aber keine Angst – es gab sechs versteckte Attribute, nicht eines!

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

Wenn ich die xmin überprüfe, sehe ich, dass die Transaktions-ID, die die letzte eingefügte Zeile eingeführt hat, (+2) höher ist (+1 war die gelöschte Zeile). Für die sequentielle Zeilenkennung könnte ich also ein völlig anderes Attribut verwenden! Natürlich ist es nicht so einfach, sonst würde eine solche Verwendung gefördert. Die xmin-Spalte vor 9.4 wurde tatsächlich überschrieben, um vor xid-Wraparound zu schützen. Warum so kompliziert? Das MVCC in Postgres ist sehr intelligent und die Methoden drumherum werden mit der Zeit immer besser. Das bringt natürlich Komplexität. Ach. Einige Leute möchten sogar Systemspalten vermeiden. Doppelt leider. Weil Systemspalten cool und gut dokumentiert sind. Das oberste Attribut (denken Sie daran, dass ich Oids überspringe) ist tableoid:

vao=# select i, tableoid from nocol;
 i | tableoid 
---+----------
   |   253952
 2 |   253952
(2 rows)
Laden Sie noch heute das Whitepaper PostgreSQL-Verwaltung und -Automatisierung mit ClusterControl herunterErfahren Sie, was Sie wissen müssen, um PostgreSQL bereitzustellen, zu überwachen, zu verwalten und zu skalierenLaden Sie das Whitepaper herunter

Es sieht nutzlos aus, in jeder Zeile den GLEICHEN Wert zu haben - nicht wahr? Und doch war es vor einiger Zeit ein sehr beliebtes Attribut - als wir alle Partitionierung mit Regeln und geerbten Tabellen erstellten. Wie würden Sie debuggen, aus welcher Tabelle die Zeile stammt, wenn nicht mit Tableoid? Wenn Sie also Regeln, Ansichten (gleiche Regeln) oder UNION verwenden, hilft Ihnen das tableoid-Attribut, die Quelle zu identifizieren:

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)

Boah was war das? Ich habe mich so sehr daran gewöhnt, INSERT 0 1 zu sehen, dass meine psql-Ausgabe komisch aussah! Ah - stimmt - ich habe eine Tabelle mit OIDs erstellt und nur einen (253967) Identifikator verzweifelt sinnlos verwendet! Nun - nicht völlig sinnlos (wenn auch verzweifelt) - die Auswahl gibt zwei Zeilen mit derselben ctid (0,1) zurück - nicht überraschend - ich wähle aus zwei Tabellen aus und füge dann Ergebnisse einer anderen hinzu, also die Chance, dieselbe ctid zu haben ist nicht so niedrig. Das letzte, was zu erwähnen ist, ist, dass ich wieder Objektbezeichnertypen verwenden kann, um es hübsch darzustellen:

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! (mit steigender Intonation gesagt) - So kann man also die Datenquelle hier eindeutig anpinnen!

Schließlich noch eine sehr beliebte und interessante Verwendung - definieren, welche Zeile eingefügt und welche eingefügt wurde:

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

Jetzt, da wir einen PK haben, kann ich die ON CONFLICT-Direktive verwenden:

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)
Verwandte Ressourcen ClusterControl for PostgreSQL Verständnis und Lesen des PostgreSQL-Systemkatalogs Eine Übersicht über die Datenbankindizierung in PostgreSQL

Warum so glücklich? Weil ich (mit einiger Vertraulichkeit) sagen kann, dass diese Zeile mit xmax ungleich Null aktualisiert wurde. Und denken Sie nicht, dass es offensichtlich ist - es sieht so aus, nur weil ich Unixtime für PK verwendet habe, also sieht es wirklich anders aus als einstellige Werte. Stellen Sie sich vor, Sie machen einen solchen ON CONFLICT-Twist an einem großen Set und es gibt keinen logischen Weg, um zu identifizieren, welcher Wert einen Konflikt hatte und welcher nicht. xmax hat Tonnen von DBAs in schweren Zeiten geholfen. Und die beste Beschreibung, wie es funktioniert, würde ich hier empfehlen - genauso wie ich allen drei Diskussionsteilnehmern (Abelisto, Erwin und Laurenz) empfehlen würde, auf andere Postgres-Tag-Fragen und -Antworten auf SO zu lesen.

Das ist es.

tableoid, xmax, xmin und ctid sind gute Freunde eines jeden DBA. Um cmax, cmin und oid nicht zu beleidigen - sie sind auch genauso gute Freunde! Aber das reicht für einen kleinen Review und ich will jetzt die Finger von der Tastatur lassen.