Postgres唯一约束与索引


156

正如我可以理解文档下面的定义是等价的:

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 2014年

29
另一方面(最近在另一个问题中提到)的一个优点是,您可以拥有部分唯一索引,例如“ Unique(foo)where bar is Null”。AFAIK,没有办法用约束来做到这一点。
IMSoP 2014年

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),我很久以前就会遇到这个问题。
安迪

Answers:


131

我对这个基本但重要的问题有些怀疑,因此我决定以身作则。

让我们来创建测试表有两列,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)

在表描述(psql中的\ d)中,您可以根据唯一索引来区分唯一约束。

独特性

为了以防万一,让我们检查唯一性。

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.

它是实际信息吗?尤其是关于部分索引
anatol

1
@anatol-是的。
klin

30

使用的另一个优势UNIQUE INDEX主场迎战UNIQUE CONSTRAINT的是,你可以很容易地DROP/ CREATE一个指标CONCURRENTLY,而与约束你不能。


4
AFAIK不可能同时删除唯一索引。postgresql.org/docs/9.3/static/sql-dropindex.html “使用此选项时需要注意一些注意事项。只能指定一个索引名称,并且不支持CASCADE选项。(因此,一个索引它支持UNIQUE或PRIMARY KEY约束不能这样丢弃)。”
拉法尔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

@ceving取决于用例。有时您想保留大小写,同时确保不区分大小写的唯一性
Sampson Crowley

2

由于各种人都提供了唯一索引优于唯一约束的优点,因此存在一个缺点:可以推迟唯一约束(仅在事务结束时检查),不能唯一索引。


考虑到所有唯一约束都具有唯一索引,这怎么可能?
克里斯

1
由于索引没有用于延迟的API,只有约束才有,因此,尽管在后台存在用于支持唯一约束的延迟机制,但无法将索引声明为可延迟或将其延迟。
Masklinn

0

我在文档中阅读了此内容:

添加table_constraint [无效]

此表单使用与相同的语法向表中添加新约束CREATE TABLE,并加上option NOT VALID,该选项当前仅适用于外键约束。如果标记了约束NOT VALID,那么将跳过可能冗长的初始检查,以验证表中的所有行均满足约束。该约束将仍然针对随后的插入或更新强制执行(也就是说,除非引用表中有匹配的行,否则它们将失败)。但是,除非使用VALIDATE CONSTRAINT选项对其进行验证,否则数据库将不假定该约束对表中的所有行均有效。

因此,我认为您通过添加约束将其称为“部分唯一性”。

并且,关于如何确保唯一性:

添加唯一约束将在约束中列出的列或一组列上自动创建唯一的B树索引。不能将仅覆盖某些行的唯一性限制写为唯一性约束,但是可以通过创建唯一的部分索引来实施这种限制。

注意:向表添加唯一约束的首选方法是ALTER TABLE…ADD CONSTRAINT。使用索引强制实施唯一约束可以被认为是不应直接访问的实现细节。但是,应该知道,无需在唯一列上手动创建索引;这样做只会复制自动创建的索引。

因此,我们应该添加约束来创建索引,以确保唯一性。

我怎么看这个问题?

“约束”旨在从语法上确保该列应唯一,它建立了一条法律,一条规则;而“索引”是语义的,关于“如何实现,如何实现唯一性,实现时唯一性意味着什么”。因此,Postgresql实现它的方式非常合乎逻辑:首先,您声明一列应该是唯一的,然后,Postgresql为您添加了添加唯一索引的实现


1
“因此,我认为通过添加约束可以将其称为“部分唯一性”。索引只能通过该where子句应用于记录的明确定义的子集,因此您可以定义记录是唯一的满足条件的IFF。这只是禁用了未定义的记录集的约束,该记录集早于要创建的约束。这是完全不同的,尽管我认为渐进式迁移很方便,但后者的用处明显不足
Masklinn

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现在该语句起作用了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.