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

Ein Überblick über den seriellen Pseudo-Datentyp für PostgreSQL

Einführung

PostgreSQL bietet nativ eine große Vielfalt an Datentypen, die viele praktische Anwendungsfälle unterstützen. Dieser Artikel stellt die spezielle Implementierung von seriellen Datentypen vor, die typischerweise für die Erstellung synthetischer Primärschlüssel verwendet werden.

Eindeutige Schlüssel

Ein grundlegendes Gebot der Datenbankentwurfstheorie ist, dass jedes Tupel (d. h. Zeile) einer Relation (d. h. Tabelle) eindeutig von anderen Tupeln identifiziert werden muss. Die Attribute oder Spalten, die zusammen ein Tupel eindeutig von allen anderen unterscheiden, werden als "Schlüssel" bezeichnet. Einige Puristen behaupten, dass jedes modellierte Objekt oder Konzept von Natur aus ein Attribut oder eine Menge von Attributen besitzt, die als Schlüssel dienen können, und dass es wichtig ist, diese Menge von Schlüsselattributen zu identifizieren und sie für die eindeutige Auswahl von Tupeln zu verwenden.

In der Praxis kann es jedoch unpraktisch sein, einen ausreichend großen Satz von Attributen zu identifizieren, die die Eindeutigkeit eines modellierten Objekts gewährleisten, und daher wenden sich Entwickler für reale Implementierungen häufig synthetischen Schlüsseln als Ersatz zu. Das heißt, anstatt sich auf irgendeine Kombination tatsächlicher Attribute zu verlassen, wird ein Wert innerhalb der Datenbank, typischerweise inkrementierte ganzzahlige Werte, und der ansonsten keine physikalische Bedeutung hat, als Schlüssel definiert. Zusätzlich zur Einfachheit eines einzelnen Spaltenschlüssels bedeutet die Tatsache, dass es keine realen Abhängigkeiten gibt, dass externe Faktoren niemals eine Notwendigkeit erzwingen können, den Wert zu ändern, wie dies beispielsweise der Fall sein könnte, wenn der Name einer Person verwendet wird als Schlüssel ... und dann hat die Person geheiratet oder ist in ein Zeugenschutzprogramm der Bundesregierung eingetreten und hat ihren Namen geändert. Sogar einige Werte, die von Laien allgemein als einzigartig und unveränderlich angesehen werden, wie die US-Sozialversicherungsnummer, sind beides nicht:Eine Person kann eine neue Sozialversicherungsnummer erhalten, und Sozialversicherungsnummern werden manchmal wiederverwendet.

Deklarieren eines seriellen Datentyps

PostgreSQL bietet eine spezielle Datentypdeklaration, um diesen Bedarf an synthetischen Schlüsseln zu decken. Das Deklarieren einer Datenbanktabellenspalte als Typ SERIAL erfüllt die Anforderung für synthetische Schlüssel, indem beim Einfügen neuer Tupel eindeutige Ganzzahlen bereitgestellt werden. Dieser Pseudodatentyp implementiert eine Spalte vom Datentyp Integer mit einem zugeordneten Standardwert, der über einen Funktionsaufruf abgeleitet wird, der inkrementierte Integerwerte liefert. Führen Sie den folgenden Code aus, um eine einfache Tabelle mit einer ID-Spalte vom Typ Serial zu erstellen:

CREATE TABLE person (id serial, full_name text);
actually executes the following DDL
CREATE TABLE person (
    id integer NOT NULL,
    full_name text
);

CREATE SEQUENCE person_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
ALTER SEQUENCE person_id_seq OWNED BY person.id;
ALTER TABLE ONLY person
    ALTER COLUMN id
    SET DEFAULT nextval('person_id_seq'::regclass);

Das heißt, das Schlüsselwort „serial“ als Datentypspezifikation impliziert die Ausführung von DDL-Anweisungen, die eine Spalte vom Typ Integer mit einer NOT NULL-Einschränkung, einer SEQUENCE, erstellen, und dann ist der Spaltenstandardwert ALTERED, um eine integrierte Funktion aufzurufen, die auf diese SEQUENCE zugreift.

Die eingebaute Funktion nextval führt einen Autoincrement-Dienst durch:Jedes Mal, wenn nextval aufgerufen wird, erhöht sie den angegebenen Sequenzzähler und gibt den neu inkrementierten Wert zurück.

Sie können das Ergebnis dieses Effekts sehen, indem Sie die Tabellendefinition untersuchen:

postgres=# \d person
                   Table "public.person"
  Column   |  Type   |         Modifiers
-----------+---------+-----------------------------------------
 Id        | integer | not null default nextval('person_id_seq'::regclass)
 full_name | text    |

Serielle Werte einfügen

Um die Auto-Increment-Funktionalität zu nutzen, fügen wir einfach Zeilen ein und verlassen uns dabei auf den Standardwert für die serielle Spalte:

INSERT INTO person (full_name) VALUES ('Alice');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
(1 row)

Wir sehen, dass ein Wert für die ID-Spalte, die der neuen „Alice“-Zeile entspricht, automatisch generiert wurde. Alternativ kann das DEFAULT-Schlüsselwort verwendet werden, wenn eine explizite Auflistung aller Spaltennamen gewünscht wird:

INSERT INTO person (id, full_name) VALUES (DEFAULT, 'Bob');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
(2 rows)

wo wir die Auto-Increment-Funktion deutlicher sehen, die den Serially-Next-Wert der neuen Zeile für die zweite Einfügung von „Bob“ zuweist.

Das Einfügen mehrerer Zeilen funktioniert sogar:

INSERT INTO person (full_name) VALUES ('Cathy'), ('David');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
(4 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

Fehlende Seriennummern

Die integrierte nextval-Funktion ist für nicht blockierende Anwendungen mit hoher Parallelität optimiert und berücksichtigt daher kein Rollback. Folglich bedeutet dies, dass möglicherweise Werte in der Sequenz fehlen. Unten setzen wir eine Einfügung zurück, sehen dann aber, dass eine nachfolgende Einfügung einen neuen Wert erhält, der den Wert überspringt, der der abgebrochenen Transaktion zugeordnet worden wäre:

BEGIN TRANSACTION;
INSERT INTO person (full_name) VALUES ('Eve');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
  5 | Eve
(5 rows)
ROLLBACK;
INSERT INTO person (full_name) VALUES ('Fred');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
  6 | Fred
(5 rows)

Der Vorteil, Rollbacks nicht zu berücksichtigen, besteht darin, dass andere Sitzungen, die gleichzeitig Einfügungen versuchen, nicht durch andere Einfügungssitzungen blockiert werden.

Eine andere Möglichkeit, fehlende Werte zu erhalten, besteht darin, Zeilen zu löschen:

DELETE FROM person WHERE full_name = 'Cathy';
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  6 | Fred
(4 rows)

Beachten Sie, dass selbst nach dem Löschen der zuletzt eingefügten Zeile, die dem größten Wert der Auto-Increment-ID-Spalte entspricht, der Sequenzzähler nicht zurückgesetzt wird, d. h. selbst nach dem Löschen der Zeile, die 'Fred' entspricht, bleibt der Sequenzzähler für nachfolgende Einfügungen erhalten den bisher bekannten größten Wert und Inkremente von dort:

DELETE FROM person WHERE full_name = 'Fred';
INSERT INTO person (full_name) VALUES ('Gina');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
(4 rows)

Lücken oder fehlende Werte, wie oben gezeigt, werden Berichten zufolge von einigen Anwendungsentwicklern als Problem angesehen, da auf der PostgreSQL General-Mailingliste langsam, aber stetig die Frage wiederholt wird, wie Sequenzlücken bei der Verwendung des seriellen Pseudodatentyps vermieden werden können. Manchmal gibt es keine eigentliche zugrunde liegende Geschäftsanforderung, es ist nur eine Frage der persönlichen Abneigung gegen fehlende Werte. Aber es gibt Umstände, unter denen das Verhindern fehlender Nummern wirklich notwendig ist, und das ist das Thema für einen späteren Artikel.

NEIN KANNST DU NICHT - JA KANNST DU!

Die durch den seriellen Pseudodatentyp imputierte NOT NULL-Einschränkung schützt vor dem Einfügen von NULL für die ID-Spalte, indem solche Einfügungsversuche zurückgewiesen werden:

INSERT INTO person (id, full_name) VALUES (NULL, 'Henry');
ERROR:  null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, Henry).

Somit haben wir die Gewissheit, einen Wert für dieses Attribut zu haben.

Ein Problem, auf das manche Leute stoßen, ist jedoch, dass, wie oben erklärt, nichts das explizite Einfügen von Werten verhindert, indem der standardmäßige Autoincrement-Wert umgangen wird, der durch Aufruf der nextval-Funktion abgeleitet wird:

INSERT INTO person (id, full_name) VALUES (9, 'Ingrid');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
(5 rows)

Aber dann erzeugen zwei Einfügungen später unter Verwendung des Standardwertes einen doppelten Wert für die ID-Spalte, wenn es keine Einschränkungsprüfung der Spaltenwerte gegen den Sequenzwert gibt:

INSERT INTO person (full_name) VALUES ('James');
INSERT INTO person (full_name) VALUES ('Karen');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
  8 | James
  9 | Karen
(7 rows)

Wenn wir die Spalte mit der Seriennummer tatsächlich als Schlüssel verwenden würden, hätten wir sie als PRIMARY KEY deklariert oder zumindest einen UNIQUE INDEX erstellt. Hätten wir das getan, wäre die obige Einfügung „Karen“ mit einem doppelten Schlüsselfehler fehlgeschlagen. Die neueste Version von PostgreSQL enthält eine neue Einschränkungsdeklarationssyntax, die standardmäßig als Identität generiert wird und diese Fallstricke und einige andere ältere Probleme im Zusammenhang mit dem seriellen Pseudodatentyp vermeidet.

Sequenzmanipulationsfunktionen

Neben der bereits erwähnten nextval-Funktion, die die Sequenz vorrückt und den neuen Wert zurückliefert, gibt es einige weitere Funktionen zum Abfragen und Setzen der Sequenzwerte:Die currval-Funktion liefert den zuletzt mit nextval erhaltenen Wert für die angegebene Sequenz, die lastval-Funktion gibt den zuletzt mit nextval erhaltenen Wert für eine beliebige Sequenz zurück, und die Funktion setval setzt den aktuellen Wert einer Sequenz. Diese Funktionen werden mit einfachen Abfragen aufgerufen, zum Beispiel

SELECT currval('person_id_seq');
 currval
---------
       9
(1 row)

Und beachten Sie, dass, wenn ein Aufruf an die nextval-Funktion unabhängig von der tatsächlichen Ausführung einer Einfügung erfolgt, die Sequenz erhöht wird, und dies wird in nachfolgenden Einfügungen widergespiegelt:

SELECT nextval('person_id_seq');
 nextval
---------
      10
(1 row)
INSERT INTO person (full_name) VALUES ('Larry');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
  8 | James
  9 | Karen
 11 | Larry
(8 rows)

Schlussfolgerung

Wir haben ein grundlegendes Verständnis des Pseudodatentyps SERIAL von PostgreSQL für automatisch inkrementierte synthetische Schlüssel eingeführt. Zur Veranschaulichung in diesem Artikel haben wir die Typdeklaration SERIAL verwendet, die eine 4-Byte-Ganzzahlspalte erstellt. PostgreSQL erfüllt unterschiedliche Bereichsanforderungen mit den Pseudodatentypen SMALLSERIAL und BIGSERIAL für 2-Byte- bzw. 8-Byte-Spaltengrößen. Suchen Sie nach einem zukünftigen Artikel über eine Möglichkeit, den Bedarf an Sequenzen ohne fehlende Werte zu decken.