Unikalne ograniczenie Postgres a indeks

157

Jak rozumiem dokumentację, poniższe definicje są równoważne:

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);    

Jednak uwaga w podręczniku Postgres 9.4 mówi:

Preferowanym sposobem dodania unikatowego ograniczenia do tabeli jest ALTER TABLE ... ADD CONSTRAINT. Stosowanie indeksów do wymuszania unikatowych ograniczeń można uznać za szczegół implementacji, do którego nie należy uzyskiwać bezpośredniego dostępu.

(Edycja: ta notatka została usunięta z podręcznika wraz z Postgres 9.5.)

Czy to tylko kwestia dobrego stylu? Jakie są praktyczne konsekwencje wyboru jednego z tych wariantów (np. W wykonaniu)?

Adam Piotrowski
źródło
23
(Jedyną) praktyczną różnicą jest to, że można utworzyć klucz obcy z unikalnym ograniczeniem, ale nie z unikalnym indeksem.
a_horse_with_no_name
29
Zaletą z drugiej strony ( jak pojawiło się ostatnio w innym pytaniu ) jest to, że możesz mieć częściowy unikalny indeks, taki jak „Unique (foo) Where bar Is Null”. AFAIK, nie da się tego zrobić z ograniczeniami.
IMSoP
3
@a_horse_with_no_name Nie jestem pewien, kiedy to się stało, ale wydaje się, że to już nie prawda. To skrzypce SQL umożliwia odwoływanie się do kluczy obcych do unikalnego indeksu: sqlfiddle.com/#!17/20ee9 ; EDYCJA: dodanie
``
1
z dokumentacji postgres: PostgreSQL automatycznie tworzy unikalny indeks, gdy dla tabeli zdefiniowane jest unikalne ograniczenie lub klucz podstawowy. postgresql.org/docs/9.4/static/indexes-unique.html
maggu
Zgadzam się z @ user1935361, gdyby nie można było utworzyć klucza obcego do unikalnego indeksu (przynajmniej z PG 10), już dawno bym się z tym spotkał.
Andy

Odpowiedzi:

132

Miałem wątpliwości co do tej podstawowej, ale ważnej kwestii, więc postanowiłem uczyć się na przykładzie.

Utwórzmy wzorzec tabeli testowej z dwiema kolumnami, con_id z unikalnym ograniczeniem i ind_id indeksowanym przez unikalny indeks.

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)

W opisie tabeli (\ d w psql) można odróżnić unikalne ograniczenie z unikalnego indeksu.

Wyjątkowość

Sprawdźmy na wszelki wypadek wyjątkowość.

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=#

Działa zgodnie z oczekiwaniami!

Klucz obcy

Teraz zdefiniujemy tabelę szczegółów z dwoma kluczami obcymi odwołującymi się do naszych dwóch kolumn w master .

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)

Cóż, żadnych błędów. Upewnijmy się, że to działa.

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=#

Do obu kolumn można się odwoływać w kluczach obcych.

Ograniczenie za pomocą indeksu

Możesz dodać ograniczenie tabeli za pomocą istniejącego unikalnego indeksu.

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)

Teraz nie ma różnicy między opisem ograniczeń kolumny.

Indeksy częściowe

W deklaracji ograniczeń tabeli nie można tworzyć indeksów częściowych. Pochodzi bezpośrednio z definicji z create table .... W unikalnej deklaracji indeksu można ustawić WHERE clausetworzenie indeksu częściowego. Możesz również utworzyć indeks na wyrażeniu (nie tylko na kolumnie) i zdefiniować inne parametry (sortowanie, porządek sortowania, umieszczanie wartości NULL).

Nie można dodać ograniczenia tabeli przy użyciu indeksu częściowego.

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.
klin
źródło
czy to rzeczywiste informacje? zwłaszcza o indeksach cząstkowych
anatol
1
@anatol - tak, jest.
klin
30

Kolejną zaletą używania UNIQUE INDEXvs. UNIQUE CONSTRAINTjest to, że możesz łatwo DROP/ CREATEindeksować CONCURRENTLY, podczas gdy z ograniczeniem nie możesz.

Vadim Zingertal
źródło
4
AFAIK nie jest możliwe jednoczesne usunięcie unikalnego indeksu. postgresql.org/docs/9.3/static/sql-dropindex.html „Jest kilka zastrzeżeń, o których należy pamiętać podczas korzystania z tej opcji. Można podać tylko jedną nazwę indeksu, a opcja CASCADE nie jest obsługiwana. (Dlatego indeks obsługujący ograniczenie UNIQUE lub PRIMARY KEY nie może zostać w ten sposób usunięty.) ”
Rafał Cieślak
15

Wyjątkowość jest ograniczeniem. Zdarza się, że jest to realizowane poprzez utworzenie unikalnego indeksu, ponieważ indeks jest w stanie szybko przeszukać wszystkie istniejące wartości w celu określenia, czy dana wartość już istnieje.

Koncepcyjnie indeks jest szczegółem implementacyjnym, a unikalność powinna być kojarzona tylko z ograniczeniami.

Pełny tekst

Więc wydajność prędkości powinna być taka sama

Eugen Konkov
źródło
4

Inną rzeczą, z którą się spotkałem, jest to, że możesz używać wyrażeń sql w unikalnych indeksach, ale nie w ograniczeniach.

Więc to nie działa:

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

ale następujące prace.

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));
김민준
źródło
Użyłbym citextrozszerzenia.
ceving
@ceving zależy od przypadku użycia. czasami chcesz zachować osłonkę, jednocześnie zapewniając unikalność wielkości liter
Sampson Crowley
2

Ponieważ różni ludzie zapewniają przewagę unikalnych indeksów nad unikalnymi ograniczeniami, tutaj jest wada: unikalne ograniczenie można odroczyć (sprawdzane tylko na końcu transakcji), unikalnego indeksu nie można.

Masklinn
źródło
Jak to możliwe, biorąc pod uwagę, że wszystkie unikalne ograniczenia mają unikalny indeks?
Chris
1
Ponieważ indeksy nie mają interfejsu API do odroczenia, robią to tylko ograniczenia, więc chociaż mechanizm odroczenia istnieje pod osłoną, aby obsługiwać unikalne ograniczenia, nie ma możliwości zadeklarowania indeksu jako odroczonego lub odroczenia go.
Masklinn
0

Przeczytałem to w dokumencie:

DODAJ table_constraint [NOT VALID]

Ten formularz dodaje nowe ograniczenie do tabeli przy użyciu tej samej składni, co CREATE TABLEplus opcję NOT VALID, która jest obecnie dozwolona tylko w przypadku ograniczeń klucza obcego. Jeśli ograniczenie jest zaznaczone NOT VALID, pomijane jest potencjalnie długotrwałe wstępne sprawdzenie, czy wszystkie wiersze w tabeli spełniają ograniczenie . Ograniczenie będzie nadal wymuszane w odniesieniu do kolejnych wstawień lub aktualizacji (to znaczy, że zawiodą, chyba że w tabeli, do której się odwołuje, nie ma pasującego wiersza). Jednak baza danych nie przyjmie, że ograniczenie będzie obowiązywać dla wszystkich wierszy w tabeli, dopóki nie zostanie sprawdzone za pomocą opcji VALIDATE CONSTRAINT.

Myślę więc, że jest to coś, co nazywasz „częściową wyjątkowością”, dodając ograniczenie.

A jak zadbać o wyjątkowość:

Dodanie unikalnego ograniczenia spowoduje automatyczne utworzenie unikatowego indeksu B-drzewa w kolumnie lub grupie kolumn wymienionych w ograniczeniu. Ograniczenie dotyczące unikalności obejmujące tylko niektóre wiersze nie może być zapisane jako unikalne ograniczenie, ale można wymusić takie ograniczenie, tworząc unikalny indeks częściowy.

Uwaga: Preferowanym sposobem dodania unikatowego ograniczenia do tabeli jest ALTER TABLE… ADD CONSTRAINT. Stosowanie indeksów do wymuszania unikatowych ograniczeń można uznać za szczegół implementacji, do którego nie należy uzyskiwać bezpośredniego dostępu. Należy jednak mieć świadomość, że nie ma potrzeby ręcznego tworzenia indeksów na unikalnych kolumnach; spowodowałoby to po prostu zduplikowanie automatycznie utworzonego indeksu.

Powinniśmy więc dodać ograniczenie, które tworzy indeks, aby zapewnić unikalność.

Jak widzę ten problem?

„Ograniczenie” ma na celu gramatyczne zapewnienie, że ta kolumna będzie niepowtarzalna, ustanawia prawo, regułę; podczas gdy „indeks” jest semantyczny , dotyczy tego, „jak wdrożyć, jak osiągnąć wyjątkowość, co oznacza unikalność, jeśli chodzi o implementację”. Tak więc sposób, w jaki Postgresql to implementuje, jest bardzo logiczny: najpierw deklarujesz, że kolumna powinna być unikalna, a następnie Postgresql dodaje implementację dodawania unikalnego indeksu .

WesternGun
źródło
1
„Więc myślę, że to jest to, co nazywasz„ częściową wyjątkowością ”poprzez dodanie ograniczenia”. indeksy mogą dotyczyć tylko dobrze zdefiniowanego podzbioru rekordów za pomocą whereklauzuli, więc można zdefiniować, że rekordy są unikalne. IFF spełniają pewne kryteria. To po prostu wyłącza ograniczenia dla niezdefiniowanego zestawu rekordów, które poprzedzają utworzenie ograniczenia. Jest zupełnie inaczej, a ta ostatnia jest znacznie mniej przydatna, choć wydaje mi się, że jest wygodna w przypadku migracji progresywnych.
Masklinn
0

Istnieje różnica w blokowaniu.
Dodanie indeksu nie blokuje dostępu do odczytu do tabeli.
Dodanie ograniczenia powoduje zablokowanie tabeli (więc wszystkie selekcje są blokowane), ponieważ jest ono dodawane za pośrednictwem ALTER TABLE .

Bax
źródło
0

Bardzo drobną rzeczą, którą można zrobić tylko z ograniczeniami, a nie z indeksami, jest użycie ON CONFLICT ON CONSTRAINTklauzuli ( zobacz również to pytanie ).

To nie działa:

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 *;

Produkuje:

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

Zmień indeks w ograniczenie:

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

To INSERTstwierdzenie teraz działa.

Lukas Eder
źródło