Introduktion
PostgreSQL leverer naturligt en rig mangfoldighed af datatyper, der understøtter mange praktiske use cases. Denne artikel introducerer den særlige implementering af serielle datatyper, der typisk bruges til oprettelse af syntetiske primære nøgler.
Unikke nøgler
En grundlæggende forskrift for databasedesignteori er, at hver tuple (dvs. række) i en relation (dvs. tabel) skal identificeres entydigt fra andre tuples. De attributter eller kolonner, der tilsammen tydeligt identificerer en tupel fra alle de andre, kaldes en "nøgle". Nogle purister hævder, at ethvert modelleret objekt eller koncept i sagens natur besidder en egenskab eller et sæt af egenskaber, der kan tjene som en nøgle, og at det er vigtigt at identificere dette sæt nøgleattributter og bruge dem til det unikke udvalg af tupler.
Men som en praktisk sag, kan det være upraktisk at identificere et tilstrækkeligt stort sæt attributter, der sikrer unikhed for et modelleret objekt, og så for implementeringer i den virkelige verden, vender udviklere sig ofte til syntetiske nøgler som en surrogat. Det vil sige, i stedet for at stole på en eller anden kombination af faktiske attributter, defineres en værdi internt i databasen, typisk øgede heltalsværdier, og som ellers ikke har nogen fysisk betydning, som en nøgle. Ud over enkelheden ved en enkelt kolonnenøgle betyder det faktum, at der ikke er nogen afhængighed i den virkelige verden, at eksterne faktorer aldrig kan fremtvinge et behov for at ændre værdien, som f.eks. kan være tilfældet, hvis en persons navn blev brugt som en nøgle ... og så giftede personen sig eller gik ind i et vidnebeskyttelsesprogram fra den føderale regering og skiftede navn. Selv nogle værdier, der almindeligvis af lægfolk anses for at være unikke og uforanderlige, såsom det amerikanske personnummer, er ingen af dem:en person kan få et nyt SSN, og SSN'er genbruges nogle gange.
Erklæring af en seriel datatype
PostgreSQL leverer en speciel datatypedeklaration for at tilfredsstille dette behov for syntetiske nøgler. Erklæring af en databasetabelkolonne som type SERIAL opfylder kravet til syntetiske nøgler ved at levere unikke heltal ved indsættelse af nye tupler. Denne pseudo-datatype implementerer en heltalsdatatypekolonne med en tilknyttet standardværdi afledt via et funktionskald, der leverer øgede heltalsværdier. Udførelse af følgende kode for at skabe en simpel tabel med en id-kolonne af typen seriel:
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);
Det vil sige, at nøgleordet "serial" som en datatypespecifikation indebærer udførelse af DDL-sætninger, der skaber en heltalstypekolonne med en NOT NULL-begrænsning, en SEQUENCE, og derefter ændres kolonnens standardindstilling for at kalde en indbygget funktion, der får adgang til den SEQUENCE.
Den indbyggede funktion nextval udfører en autoincrement service:hver gang nextval kaldes, øger den den angivne sekvenstæller og returnerer den nyligt inkrementerede værdi.
Du kan se resultatet af denne effekt ved at undersøge tabeldefinitionen:
postgres=# \d person
Table "public.person"
Column | Type | Modifiers
-----------+---------+-----------------------------------------
Id | integer | not null default nextval('person_id_seq'::regclass)
full_name | text |
Indsættelse af serieværdier
For at gøre brug af den automatiske inkrement-funktionalitet indsætter vi blot rækker, baseret på standardværdien for den serielle kolonne:
INSERT INTO person (full_name) VALUES ('Alice');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
(1 row)
Vi ser, at en værdi for id-kolonnen, der svarer til den nye "Alice"-række, er blevet automatisk genereret. Alternativt kan man bruge søgeordet DEFAULT, hvis det ønskes eksplicit at angive alle kolonnenavne:
INSERT INTO person (id, full_name) VALUES (DEFAULT, 'Bob');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
(2 rows)
hvor vi ser den automatiske inkrement-funktionalitet mere åbenlyst, idet vi tildeler den serielt næste værdi til den nye række for den anden indsættelse af "Bob".
Indsættelse af flere rækker virker endda:
INSERT INTO person (full_name) VALUES ('Cathy'), ('David');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
3 | Cathy
4 | David
(4 rows)
Download Whitepaper Today PostgreSQL Management &Automation med ClusterControlFå flere oplysninger om, hvad du skal vide for at implementere, overvåge, administrere og skalere PostgreSQLDownload Whitepaper Manglende serieværdier
Den indbyggede nextval-funktion er optimeret til ikke-blokerende applikationer med høj samtidighed og respekterer derfor ikke rollback. Det betyder derfor, at der kan mangle værdier i sekvensen. Nedenfor ruller vi en indsættelse tilbage, men ser så, at en efterfølgende indsættelse får en ny værdi, der springer over den værdi, der ville have været forbundet med den afbrudte transaktion:
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)
Fordelen ved ikke at respektere rollbacks er, at andre sessioner, der forsøger samtidige indsættelser, ikke blokeres af andre indsættelsessessioner.
En anden måde at ende med manglende værdier er, hvis rækker slettes:
DELETE FROM person WHERE full_name = 'Cathy';
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
4 | David
6 | Fred
(4 rows)
Bemærk, at selv efter sletning af den senest indsatte række svarende til den største auto-increment id-kolonneværdi, vender sekvenstælleren ikke tilbage, dvs. selvom sekvenstælleren stadig beholder efter sletning af rækken svarende til 'Fred' for efterfølgende indsættelser den tidligere kendte største værdi og stigninger derfra:
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)
Huller eller manglende værdier som vist ovenfor er angiveligt set som et problem af nogle applikationsudviklere, fordi der på PostgreSQL General-mailinglisten er en langsom, men konstant gentagelse af spørgsmålet, hvordan man undgår sekvenshuller, når man bruger den serielle pseudo-datatype. Nogle gange er der ingen egentlige underliggende forretningskrav, det er bare et spørgsmål om personlig modvilje mod manglende værdier. Men der er omstændigheder, hvor det er et reelt behov at forhindre manglende numre, og det er emnet for en efterfølgende artikel.
NEJ DU KAN IKKE - JA DU KAN!
NOT NULL-begrænsningen, som tilskrives af den serielle pseudo-datatype, beskytter mod indsættelse af NULL for id-kolonnen ved at afvise sådanne indsættelsesforsøg:
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).
Vi er således sikret en værdi for denne egenskab.
Et problem, som nogle mennesker støder på, er, at der, som erklæret ovenfor, intet forhindrer eksplicit indsættelse af værdier, og omgår standard autoincrement-værdien afledt via påkaldelse af nextval-funktionen:
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)
Men to indsættelser senere ved brug af standarden producerer en dubletværdi for id-kolonnen, hvis der ikke er nogen begrænsningskontrol af kolonneværdier mod sekvensværdien:
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)
Hvis vi rent faktisk brugte den serielle id-kolonne som en nøgle, ville vi have erklæret den som en PRIMÆR NØGLE eller i det mindste oprettet et UNIKT INDEX. Havde vi gjort det, så ville 'Karen'-indsættelsen ovenfor have fejlet med en dubletnøglefejl. Den seneste udgivelse af PostgreSQL indeholder en ny begrænsningserklæringssyntaks 'genereret som standard som identitet', som undgår denne faldgrube og nogle andre ældre problemer relateret til den serielle pseudo-datatype.
Sekvensmanipulationsfunktioner
Ud over den nextval-funktion, som vi allerede har nævnt, som fremfører sekvensen og returnerer den nye værdi, er der et par andre funktioner til at forespørge og indstille sekvensværdierne:currval-funktionen returnerer den værdi, der senest er opnået med nextval for specificeret sekvens, lastval-funktionen returnerer den senest opnåede værdi med nextval for enhver sekvens, og setval-funktionen indstiller en sekvenss aktuelle værdi. Disse funktioner kaldes med simple forespørgsler., for eksempel
SELECT currval('person_id_seq');
currval
---------
9
(1 row)
Og bemærk, at hvis der foretages et opkald til nextval-funktionen uafhængigt af faktisk at udføre en insert, øger den sekvensen, og det vil blive afspejlet i efterfølgende inserts:
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)
Konklusion
Vi har introduceret en grundlæggende forståelse af PostgreSQL SERIAL pseudo-datatypen for automatisk inkrementerede syntetiske nøgler. Til illustration i denne artikel brugte vi SERIAL-typedeklarationen, som opretter en 4-byte heltalskolonne. PostgreSQL imødekommer forskellige rækkeviddebehov med SMALLSERIAL og BIGSERIAL pseudo-datatyperne for henholdsvis 2-byte og 8-byte kolonnestørrelser. Se efter en fremtidig artikel om et middel til at adressere behovet for sekvenser uden manglende værdier.