Постгрес унікальне обмеження проти індексу


157

Як я можу зрозуміти документацію, такі визначення є рівнозначними:

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

Однак у примітці до Постгресу 9.4 в примітці написано:

Кращим способом додати до таблиці унікальне обмеження є ALTER TABLE ... ADD CONSTRAINT. Використання індексів для забезпечення унікальних обмежень може вважатися деталізацією реалізації, до якої не слід звертатися безпосередньо.

(Редагувати: цю примітку було видалено з посібника із Postgres 9.5.)

Це лише питання хорошого стилю? Які практичні наслідки вибору одного з цих варіантів (наприклад, у виконанні)?


23
(Єдина) практична відмінність полягає в тому, що ви можете створити іноземний ключ для унікального обмеження, але не для унікального індексу.
a_horse_with_no_name

29
Перевага навпаки ( як це з’явилося в іншому питанні нещодавно ) полягає в тому, що у вас може бути частковий унікальний індекс, наприклад "Unique (foo) Where bar Is Null". AFAIK, немає ніякого способу зробити це з обмеженням.
IMSoP

3
@a_horse_with_no_name Я не впевнений, коли це сталося, але це більше не відповідає дійсності. Ця скрипка SQL дозволяє посилатися на зовнішні ключові посилання на унікальний індекс: sqlfiddle.com/#!17/20ee9 ; EDIT: додавання "фільтра" до унікального індексу призводить до припинення роботи (як очікувалося)
user1935361

1
з документації postgres: PostgreSQL автоматично створює унікальний індекс, коли для таблиці визначено унікальне обмеження або первинний ключ. postgresql.org/docs/9.4/static/indexes-unique.html
maggu

Я погоджуюся з @ user1935361, якби не було можливості створити зовнішній ключ до унікального індексу (принаймні з PG 10), я б давно стикався з цим питанням.
Енді

Відповіді:


132

У мене були деякі сумніви щодо цього основного, але важливого питання, тому я вирішив вчитися на прикладі.

Давайте створимо майстер тестової таблиці з двома стовпцями, con_id з унікальним обмеженням та ind_id, індексованим унікальним індексом.

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)

В описі таблиці (\ d в psql) ви можете вказати унікальне обмеження від унікального індексу.

Унікальність

Давайте перевіримо унікальність, про всяк випадок.

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

Це працює як очікувалося!

Іноземні ключі

Тепер ми визначимо таблицю деталей з двома зовнішніми ключами, що посилаються на наші два стовпці в майстрі .

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)

Ну ніяких помилок. Давайте переконаємось, що це працює.

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

На обидва стовпці можна посилатись іноземними ключами.

Обмеження за допомогою індексу

Ви можете додати обмеження таблиці, використовуючи існуючий унікальний індекс.

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)

Тепер різниці між описом обмежень стовпців немає.

Часткові показники

У декларації обмежень таблиці ви не можете створити часткові індекси. Він поставляється безпосередньо з визначення про create table .... В унікальній декларації індексу можна налаштувати WHERE clauseстворення часткового індексу. Ви також можете створити індекс на вираження (не тільки у стовпці) та визначити деякі інші параметри (зіставлення, порядок сортування, розміщення NULL).

Не можна додати обмеження таблиці за допомогою часткового індексу.

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.

це фактична інформація? особливо про часткові індекси
анатоль

1
@anatol - так, так і є.
клин

30

Ще однією перевагою використання UNIQUE INDEXvs. UNIQUE CONSTRAINTє те, що ви можете легко DROP/ CREATEіндексувати CONCURRENTLY, тоді як з обмеженням ви не можете.


4
AFAIK неможливо одночасно знизити унікальний індекс. postgresql.org/docs/9.3/static/sql-dropindex.html "Існує кілька застережень, про які слід пам'ятати при використанні цього параметра. Можна вказати лише одне ім'я індексу, а опція CASCADE не підтримується. (Таким чином, індекс що підтримує УНІКАЛЬНЕ або ПЕРВИЧНЕ КЛЮЧОВЕ обмеження не можна таким чином скинути.) "
Rafał Cieślak

15

Унікальність - це обмеження. Це може бути реалізовано за допомогою створення унікального індексу, оскільки індекс швидко може здійснити пошук усіх існуючих значень для того, щоб визначити, чи є вже задане значення.

Концептуально індекс є деталізацією реалізації, і унікальність повинна асоціюватися лише з обмеженнями.

Повний текст

Тому швидкість роботи повинна бути однаковою


4

Ще одна річ, з якою я стикався, це те, що ви можете використовувати вирази sql в унікальних індексах, але не в обмеженнях.

Отже, це не працює:

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

але наступні роботи.

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

Я б використав citextрозширення.
закінчення

@ceving це залежить від випадку використання. іноді хочеться зберегти кожух, забезпечивши унікальність, що не
враховує

2

Оскільки різні люди надали переваги унікальних індексів над унікальними обмеженнями, ось недолік: унікальне обмеження можна відкласти (лише перевірити в кінці транзакції), унікального індексу бути не може.


Як це може бути, враховуючи, що всі унікальні обмеження мають унікальний індекс?
Кріс

1
Оскільки в індексах немає API для відстрочки, вони мають лише обмеження, тому, хоча механізм відкладання існує під кришкою для підтримки унікальних обмежень, немає способу оголосити індекс як відкладений або відкласти його.
Масклін

0

Я читав це в документі:

ADD table_constraint [NOT VALID]

Ця форма додає нове обмеження до таблиці, використовуючи той же синтаксис CREATE TABLE, що і плюс параметр NOT VALID, який наразі дозволений лише для обмежень іноземних ключів. Якщо обмеження позначено NOT VALID, потенційно тривала початкова перевірка, щоб перевірити, чи всі рядки таблиці задовольняють обмеженню, пропускається . Обмеження все ще буде застосовано до наступних вставок або оновлень (тобто вони вийдуть з ладу, якщо в таблиці з посиланням не буде відповідного рядка). Але база даних не буде вважати, що обмеження виконується для всіх рядків таблиці, поки воно не буде перевірено за допомогою параметра VALIDATE CONSTRAINT.

Тому я думаю, що це те, що ви називаєте "частковою унікальністю", додаючи обмеження.

І про те, як забезпечити унікальність:

Додавання унікального обмеження автоматично створить унікальний індекс B-дерева на стовпчику або групі стовпців, перелічених у обмеженні. Унікальне обмеження, що охоплює лише деякі рядки, не може бути записане як унікальне обмеження, але можливо застосувати таке обмеження, створивши унікальний частковий індекс.

Примітка. Кращим способом додавання унікального обмеження до таблиці є ALTER TABLE… ADD CONSTRAINT. Використання індексів для забезпечення унікальних обмежень може вважатися деталізацією реалізації, до якої не слід звертатися безпосередньо. Однак слід пам’ятати, що немає потреби вручну створювати індекси на унікальних стовпцях; це просто дублює автоматично створений індекс.

Тому ми повинні додати обмеження, яке створює індекс, щоб забезпечити унікальність.

Як я бачу цю проблему?

"Обмеження" має на меті граматично забезпечити унікальність цього стовпця, він встановлює закон, правило; в той час як "індекс" є семантичним , про "як реалізувати, як досягти унікальності, що означає унікальний спосіб, коли мова йде про реалізацію". Отже, те, як Postgresql реалізує це, є дуже логічним: спочатку ви заявляєте, що стовпець повинен бути унікальним, потім, Postgresql додає реалізацію додавання унікального для вас індексу .


1
"Тож я думаю, це те, що ви називаєте" частковою унікальністю ", додаючи обмеження". Індекси можуть застосовуватися лише до чітко визначеного підмножини записів за допомогою whereпункту, тому ви можете визначити, що записи є унікальними IFF, вони відповідають деяким критеріям. Це просто вимикає обмеження для невизначеного набору записів, які передували створюваному обмеженню. Він зовсім інший, і останній значно менш корисний, хоча, мабуть, він зручний для прогресивних міграцій.
Масклін

0

Існує різниця в блокуванні.
Додавання індексу не блокує доступ для читання до таблиці.
Додавання обмеження ставить блокування таблиці (тому всі виділення заблоковані), оскільки воно додається через ALTER TABLE .


0

Дуже незначна річ, яку можна зробити лише з обмеженнями, а не з індексами, - це використання ON CONFLICT ON CONSTRAINTпункту ( див. Також це запитання ).

Це не працює:

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

Він виробляє:

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

Перетворіть індекс на обмеження:

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

І INSERTзаява зараз працює.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.