Postgres唯一约束vs索引

时间:2014-05-08 13:12:41

标签: sql postgresql unique

据我所知documentation,以下定义是等效的:

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

但是您可以在注释中阅读:向表中添加唯一约束的首选方法是ALTER TABLE ... ADD CONSTRAINT。使用索引来强制执行唯一约束可以被视为不应直接访问的实现细节

这只是一个好风格的问题吗?选择其中一种变体(例如性能)的实际后果是什么?

9 个答案:

答案 0 :(得分:109)

我对这个基本但重要的问题有些怀疑,所以我决定通过实例学习。

让我们创建包含两列的测试表 master 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)

在表格描述(ps中的\ 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=#

按预期工作!

外键

现在我们将定义 detail 表,其中两个外键引用 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 ...的{​​{3}}。 在唯一索引声明中,您可以设置WHERE clause以创建部分索引。 您还可以definition表达式(不仅在列上)并定义一些其他参数(排序规则,排序顺序,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 :(得分:26)

使用UNIQUE INDEXUNIQUE CONSTRAINT的另一个好处是,您可以轻松地DROP / CREATE索引CONCURRENTLY,而有了约束,您可以“T

答案 2 :(得分:8)

  

唯一性是一种约束。它恰好通过创建来实现   一个唯一索引,因为索引可以快速搜索所有现有索引   值以确定给定值是否已存在。

     

从概念上讲,索引是一个实现细节,应该是唯一性   仅与约束相关联。

The full text

所以速度表现应该相同

答案 3 :(得分:4)

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

答案 4 :(得分:2)

我遇到的另一件事是你可以在唯一索引中使用sql表达式,但不能在约束中使用。

所以,这不起作用:

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

但是后续工作。

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

答案 5 :(得分:2)

使用ON CONFLICT ON CONSTRAINT子句(see also this question)只能用约束而不用索引来完成一个非常小的事情。

这不起作用:

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语句现在可以正常工作。

答案 6 :(得分:0)

我在文档中阅读了此内容

  

添加table_constraint [无效]

     

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

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

关于如何确保唯一性:

  

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

     

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

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

我怎么看这个问题?

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

答案 7 :(得分:0)

锁定有区别。
添加索引不会阻止对该表的读取访问。
添加约束确实会锁定表锁(因此所有选择都会被阻止),因为它是通过 ALTER TABLE 添加的。

答案 8 :(得分:-1)

SELECT a.phone_number,count(*) FROM public.users a
Group BY phone_number Having count(*)>1;

SELECT a.phone_number,count(*) FROM public.retailers a
Group BY phone_number Having count(*)>1;

select a.phone_number from users a inner join users b
on a.id <> b.id and a.phone_number = b.phone_number order by a.id;


select a.phone_number from retailers a inner join retailers b
on a.id <> b.id and a.phone_number = b.phone_number order by a.id
DELETE FROM
    users a
        USING users b
WHERE
    a.id > b.id
    AND a.phone_number = b.phone_number;
    
DELETE FROM
    retailers a
        USING retailers b
WHERE
    a.id > b.id
    AND a.phone_number = b.phone_number;
    
CREATE UNIQUE INDEX CONCURRENTLY users_phone_number 
ON users (phone_number);

验证:

insert into users(name,phone_number,created_at,updated_at) select name,phone_number,created_at,updated_at from users