Уникальное ограничение Postgres против индекса

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

Тем не менее, примечание в руководстве к Postgres 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 ; РЕДАКТИРОВАТЬ: добавление «фильтра» к уникальному индексу приводит к тому, что это перестает работать (как и ожидалось)
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=#

Работает как положено!

Внешние ключи

Теперь мы определим детальную таблицу с двумя внешними ключами, ссылающимися на наши два столбца в 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)

Ну, без ошибок. Давайте удостоверимся, что это работает.

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 не поддерживается. (Таким образом, индекс который поддерживает ограничение UNIQUE или PRIMARY KEY, не может быть удален таким образом.) "
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расширение.
празднование
@ зависит от варианта использования. иногда вы хотите сохранить обсадную колонну, обеспечивая при этом уникальность без
учета
2

Поскольку разные люди предоставили преимущества уникальных индексов над уникальными ограничениями, вот недостаток: уникальное ограничение может быть отложено (проверяется только в конце транзакции), уникальный индекс не может быть.

Masklinn
источник
Как это может быть, учитывая, что все уникальные ограничения имеют уникальный индекс?
Крис
1
Поскольку у индексов нет API для отсрочки, есть только ограничения, поэтому, хотя механизм покрытия существует под прикрытием для поддержки уникальных ограничений, нет способа объявить индекс как отсроченный или отложить его.
Масклинн
0

Я прочитал это в документе:

ДОБАВИТЬ table_constraint [НЕ VALID]

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

Поэтому я думаю, что это то, что вы называете «частичной уникальностью», добавляя ограничение.

И о том, как обеспечить уникальность:

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

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

Поэтому мы должны добавить ограничение, которое создает индекс, чтобы обеспечить уникальность.

Как я вижу эту проблему?

«Ограничение» нацелено на то, чтобы грамматически гарантировать, что этот столбец должен быть уникальным, он устанавливает закон, правило; в то время как «индекс» является семантическим , о «как реализовать, как добиться уникальности, что означает уникальность, когда дело доходит до реализации». Таким образом, способ, которым Postgresql реализует это, очень логичен: сначала вы объявляете, что столбец должен быть уникальным, затем Postgresql добавляет реализацию добавления уникального индекса для вас .

WesternGun
источник
1
«Поэтому я думаю, что это то, что вы называете« частичной уникальностью », добавляя ограничение». С помощью этого предложения индексы могут применяться только к четко определенному подмножеству записей where, поэтому вы можете определить, что записи являются уникальными, если они удовлетворяют некоторым критериям. Это просто отключает ограничения для неопределенного набора записей, которые предшествуют создаваемому ограничению. Это совершенно другое, и последнее значительно менее полезно, хотя я думаю, что это удобно для прогрессивных миграций.
Масклинн
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утверждение сейчас работает.

Лукас Эдер
источник