Postgres eindeutige Einschränkung gegenüber Index


154

Soweit ich die Dokumentation verstehen kann, sind die folgenden Definitionen gleichwertig:

create table foo (
    id serial primary key,
    code integer,
    label text,
    constraint foo_uq unique (code, label));

create table foo (
    id serial primary key,
    code integer,
    label text);
create unique index foo_idx on foo using btree (code, label);    

Ein Hinweis im Handbuch für Postgres 9.4 lautet jedoch:

Die bevorzugte Methode zum Hinzufügen einer eindeutigen Einschränkung zu einer Tabelle ist ALTER TABLE ... ADD CONSTRAINT. Die Verwendung von Indizes zur Durchsetzung eindeutiger Einschränkungen kann als Implementierungsdetail angesehen werden, auf das nicht direkt zugegriffen werden sollte.

(Bearbeiten: Dieser Hinweis wurde mit Postgres 9.5 aus dem Handbuch entfernt.)

Ist es nur eine Frage des guten Stils? Was sind die praktischen Konsequenzen der Wahl einer dieser Varianten (z. B. in der Leistung)?


23
Der (einzige) praktische Unterschied besteht darin, dass Sie einen Fremdschlüssel für eine eindeutige Einschränkung, jedoch nicht für einen eindeutigen Index erstellen können.
a_horse_with_no_name

29
Ein umgekehrter Vorteil ( wie kürzlich in einer anderen Frage erwähnt ) ist, dass Sie einen teilweise eindeutigen Index haben können, z. B. "Unique (foo) Where bar Is Null". AFAIK, es gibt keine Möglichkeit, dies mit einer Einschränkung zu tun.
IMSoP

3
@a_horse_with_no_name Ich bin mir nicht sicher, wann dies passiert ist, aber dies scheint nicht mehr wahr zu sein. Diese SQL-Geige ermöglicht Fremdschlüsselverweise auf einen eindeutigen Index: sqlfiddle.com/#!17/20ee9 ; BEARBEITEN: Das Hinzufügen eines 'Filters' zum eindeutigen Index führt dazu, dass dies (wie erwartet) nicht mehr
funktioniert

1
Aus der Postgres-Dokumentation: PostgreSQL erstellt automatisch einen eindeutigen Index, wenn für eine Tabelle eine eindeutige Einschränkung oder ein eindeutiger Primärschlüssel definiert wird. postgresql.org/docs/9.4/static/indexes-unique.html
maggu

Ich stimme @ user1935361 zu. Wenn es nicht möglich wäre, einen Fremdschlüssel für einen eindeutigen Index zu erstellen (mindestens mit PG 10), wäre ich vor langer Zeit auf dieses Problem gestoßen.
Andy

Antworten:


131

Ich hatte einige Zweifel an diesem grundlegenden, aber wichtigen Thema und beschloss, anhand eines Beispiels zu lernen.

Lassen Sie sich Test - Tabelle erstellen Master mit zwei Spalten, con_id mit eindeutiger Einschränkung und ind_id indiziert durch eine eindeutigen Index.

create table master (
    con_id integer unique,
    ind_id integer
);
create unique index master_unique_idx on master (ind_id);

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_unique_idx" UNIQUE, btree (ind_id)

In der Tabellenbeschreibung (\ d in psql) können Sie die eindeutige Einschränkung vom eindeutigen Index unterscheiden.

Einzigartigkeit

Lassen Sie uns die Eindeutigkeit überprüfen, nur für den Fall.

test=# insert into master values (0, 0);
INSERT 0 1
test=# insert into master values (0, 1);
ERROR:  duplicate key value violates unique constraint "master_con_id_key"
DETAIL:  Key (con_id)=(0) already exists.
test=# insert into master values (1, 0);
ERROR:  duplicate key value violates unique constraint "master_unique_idx"
DETAIL:  Key (ind_id)=(0) already exists.
test=#

Es funktioniert wie erwartet!

Fremde Schlüssel

Jetzt definieren wir eine Detailtabelle mit zwei Fremdschlüsseln, die auf unsere beiden Spalten im Master verweisen .

create table detail (
    con_id integer,
    ind_id integer,
    constraint detail_fk1 foreign key (con_id) references master(con_id),
    constraint detail_fk2 foreign key (ind_id) references master(ind_id)
);

    Table "public.detail"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Foreign-key constraints:
    "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

Nun, keine Fehler. Stellen wir sicher, dass es funktioniert.

test=# insert into detail values (0, 0);
INSERT 0 1
test=# insert into detail values (1, 0);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk1"
DETAIL:  Key (con_id)=(1) is not present in table "master".
test=# insert into detail values (0, 1);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk2"
DETAIL:  Key (ind_id)=(1) is not present in table "master".
test=#

Beide Spalten können in Fremdschlüsseln referenziert werden.

Einschränkung mit Index

Sie können Tabelleneinschränkungen mithilfe des vorhandenen eindeutigen Index hinzufügen.

alter table master add constraint master_ind_id_key unique using index master_unique_idx;

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_ind_id_key" UNIQUE CONSTRAINT, btree (ind_id)
Referenced by:
    TABLE "detail" CONSTRAINT "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    TABLE "detail" CONSTRAINT "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

Jetzt gibt es keinen Unterschied zwischen der Beschreibung der Spalteneinschränkungen.

Teilindizes

In der Deklaration von Tabelleneinschränkungen können Sie keine Teilindizes erstellen. Es kommt direkt aus der Definition von create table .... In einer eindeutigen Indexdeklaration können Sie festlegen WHERE clause, dass ein Teilindex erstellt wird. Sie können auch einen Index für den Ausdruck erstellen (nicht nur für die Spalte) und einige andere Parameter definieren (Sortierung, Sortierreihenfolge, NULL-Platzierung).

Sie können keine Tabelleneinschränkung mithilfe eines Teilindex hinzufügen.

alter table master add column part_id integer;
create unique index master_partial_idx on master (part_id) where part_id is not null;

alter table master add constraint master_part_id_key unique using index master_partial_idx;
ERROR:  "master_partial_idx" is a partial index
LINE 1: alter table master add constraint master_part_id_key unique ...
                               ^
DETAIL:  Cannot create a primary key or unique constraint using such an index.

ist es aktuelle Info? vor allem über Teilindizes
Anatol

1
@anatol - ja, das ist es.
Klin

30

Ein weiterer Vorteil der Verwendung von UNIQUE INDEXvs. UNIQUE CONSTRAINTist, dass Sie leicht DROP/ CREATEeinen Index CONCURRENTLYerstellen können, während dies mit einer Einschränkung nicht möglich ist.


4
AFAIK Es ist nicht möglich, gleichzeitig einen eindeutigen Index zu löschen. postgresql.org/docs/9.3/static/sql-dropindex.html "Bei Verwendung dieser Option sind einige Einschränkungen zu beachten. Es kann nur ein Indexname angegeben werden, und die Option CASCADE wird nicht unterstützt. (Daher ein Index das eine EINZIGARTIGE oder PRIMARY KEY-Einschränkung unterstützt, kann auf diese Weise nicht fallengelassen werden.) "
Rafał Cieślak

15

Einzigartigkeit ist eine Einschränkung. Die Implementierung erfolgt zufällig über die Erstellung eines eindeutigen Index, da ein Index schnell alle vorhandenen Werte durchsuchen kann, um festzustellen, ob ein bestimmter Wert bereits vorhanden ist.

Konzeptionell ist der Index ein Implementierungsdetail, und die Eindeutigkeit sollte nur mit Einschränkungen verknüpft werden.

Der vollständige Text

Die Geschwindigkeitsleistung sollte also gleich sein


4

Eine andere Sache, auf die ich gestoßen bin, ist, dass Sie SQL-Ausdrücke in eindeutigen Indizes verwenden können, jedoch nicht in Einschränkungen.

Das funktioniert also nicht:

CREATE TABLE users (
    name text,
    UNIQUE (lower(name))
);

aber folgende Werke.

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));

Ich würde die citextErweiterung verwenden.
Ceving

@ceving es hängt vom Anwendungsfall ab. Manchmal möchten Sie das Gehäuse erhalten und gleichzeitig die Einzigartigkeit der Groß- und Kleinschreibung nicht berücksichtigen
Sampson Crowley,

2

Da verschiedene Personen die Vorteile eindeutiger Indizes gegenüber eindeutigen Einschränkungen bereitgestellt haben, gibt es einen Nachteil: Eine eindeutige Einschränkung kann zurückgestellt werden (nur am Ende der Transaktion überprüft), ein eindeutiger Index nicht.


Wie kann dies sein, wenn alle eindeutigen Einschränkungen einen eindeutigen Index haben?
Chris

1
Da Indizes keine API zum Aufschieben haben, tun dies nur Einschränkungen. Während die Aufschubmaschinerie unter dem Deckmantel vorhanden ist, um eindeutige Einschränkungen zu unterstützen, gibt es keine Möglichkeit, einen Index als aufschiebbar zu deklarieren oder aufzuschieben.
Masklinn

0

Ich habe das im Dokument gelesen:

ADD table_constraint [NICHT GÜLTIG]

Dieses Formular fügt einer Tabelle eine neue Einschränkung hinzu, die dieselbe Syntax wie CREATE TABLEund die Option verwendet NOT VALID, die derzeit nur für Fremdschlüsseleinschränkungen zulässig ist. Wenn die Einschränkung markiert ist NOT VALID, wird die möglicherweise langwierige Erstprüfung, um sicherzustellen, dass alle Zeilen in der Tabelle die Einschränkung erfüllen, übersprungen . Die Einschränkung wird weiterhin für nachfolgende Einfügungen oder Aktualisierungen erzwungen (dh, sie schlagen fehl, es sei denn, die referenzierte Tabelle enthält eine übereinstimmende Zeile). Die Datenbank geht jedoch nicht davon aus, dass die Einschränkung für alle Zeilen in der Tabelle gilt, bis sie mit der Option VALIDATE CONSTRAINT überprüft wird.

Ich denke, es ist das, was Sie "teilweise Eindeutigkeit" nennen, indem Sie eine Einschränkung hinzufügen.

Und wie man die Einzigartigkeit sicherstellt:

Durch Hinzufügen einer eindeutigen Einschränkung wird automatisch ein eindeutiger B-Baum-Index für die in der Einschränkung aufgeführte Spalte oder Gruppe von Spalten erstellt. Eine Eindeutigkeitsbeschränkung, die nur einige Zeilen abdeckt, kann nicht als eindeutige Einschränkung geschrieben werden. Es ist jedoch möglich, eine solche Einschränkung durch Erstellen eines eindeutigen Teilindex durchzusetzen.

Hinweis: Die bevorzugte Methode zum Hinzufügen einer eindeutigen Einschränkung zu einer Tabelle ist ALTER TABLE… ADD CONSTRAINT. Die Verwendung von Indizes zur Durchsetzung eindeutiger Einschränkungen kann als Implementierungsdetail betrachtet werden, auf das nicht direkt zugegriffen werden sollte. Man sollte sich jedoch bewusst sein, dass es nicht notwendig ist, Indizes für eindeutige Spalten manuell zu erstellen. Dies würde nur den automatisch erstellten Index duplizieren.

Daher sollten wir eine Einschränkung hinzufügen, die einen Index erstellt, um die Eindeutigkeit sicherzustellen.

Wie sehe ich dieses Problem?

Eine "Einschränkung" zielt darauf ab, grammatisch sicherzustellen, dass diese Spalte eindeutig ist. Sie legt ein Gesetz, eine Regel fest. Während "Index" semantisch ist , geht es darum, "wie man implementiert, wie man die Einzigartigkeit erreicht, was bedeutet einzigartig, wenn es um die Implementierung geht". Die Art und Weise, wie Postgresql es implementiert, ist sehr logisch: Zuerst deklarieren Sie, dass eine Spalte eindeutig sein soll, dann fügt Postgresql die Implementierung hinzu, einen eindeutigen Index für Sie hinzuzufügen .


1
"Ich denke, es ist das, was Sie" teilweise Einzigartigkeit "nennen, indem Sie eine Einschränkung hinzufügen." Indizes können über die whereKlausel nur auf eine genau definierte Teilmenge der Datensätze angewendet werden. Sie können also definieren, dass Datensätze eindeutig sind, wenn sie bestimmte Kriterien erfüllen. Dadurch werden einfach die Einschränkungen für einen undefinierten Satz von Datensätzen deaktiviert, die vor der erstellten Einschränkung liegen. Es ist völlig anders und letzteres ist deutlich weniger nützlich, obwohl es für progressive Migrationen geeignet ist, denke ich.
Masklinn

0

Es gibt einen Unterschied in der Verriegelung.
Das Hinzufügen eines Index blockiert nicht den Lesezugriff auf die Tabelle.
Durch Hinzufügen einer Einschränkung wird eine Tabellensperre gesetzt (sodass alle Auswahlen blockiert sind), da sie über ALTER TABLE hinzugefügt wird .


0

Eine sehr kleine Sache, die nur mit Einschränkungen und nicht mit Indizes durchgeführt werden kann, ist die Verwendung der ON CONFLICT ON CONSTRAINTKlausel ( siehe auch diese Frage ).

Das funktioniert nicht:

CREATE TABLE T (a INT PRIMARY KEY, b INT, c INT);
CREATE UNIQUE INDEX u ON t(b);

INSERT INTO T (a, b, c)
VALUES (1, 2, 3)
ON CONFLICT ON CONSTRAINT u
DO UPDATE SET c = 4
RETURNING *;

Es produziert:

[42704]: ERROR: constraint "u" for table "t" does not exist

Verwandeln Sie den Index in eine Einschränkung:

DROP INDEX u;
ALTER TABLE t ADD CONSTRAINT u UNIQUE (b);

Und die INSERTAussage funktioniert jetzt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.