Postgres ràng buộc duy nhất so với chỉ mục


156

Như tôi có thể hiểu tài liệu các định nghĩa sau là tương đương:

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

Tuy nhiên, một lưu ý trong hướng dẫn sử dụng cho Postgres 9.4 nói:

Cách ưa thích để thêm một ràng buộc duy nhất vào bảng là ALTER TABLE ... ADD CONSTRAINT. Việc sử dụng các chỉ mục để thực thi các ràng buộc duy nhất có thể được coi là một chi tiết triển khai không nên được truy cập trực tiếp.

(Chỉnh sửa: ghi chú này đã bị xóa khỏi hướng dẫn sử dụng với Postgres 9.5.)

Có phải chỉ là một vấn đề của phong cách tốt? Hậu quả thực tế của việc lựa chọn một trong những biến thể này (ví dụ như trong hiệu suất) là gì?


23
Sự khác biệt thực tế (duy nhất) là bạn có thể tạo khóa ngoại cho một ràng buộc duy nhất nhưng không phải là một chỉ mục duy nhất.
a_horse_with_no_name

29
Một lợi thế theo cách khác ( như đã xuất hiện trong một câu hỏi khác gần đây ) là bạn có thể có một chỉ mục duy nhất một phần , chẳng hạn như "Unique (foo) Where bar Is Null". AFAIK, không có cách nào để làm điều đó với một ràng buộc.
IMSoP

3
@a_horse_with_no_name Tôi không chắc khi điều này xảy ra, nhưng điều này dường như không còn đúng nữa. Fiddle SQL này cho phép các tham chiếu khóa ngoài đến một chỉ mục duy nhất: sqlfiddle.com/#!17/20ee9 ; EDIT: thêm 'bộ lọc' vào chỉ mục duy nhất khiến điều này ngừng hoạt động (như mong đợi)
user1935361

1
từ tài liệu postgres: PostgreSQL tự động tạo một chỉ mục duy nhất khi một ràng buộc duy nhất hoặc khóa chính được xác định cho một bảng. postgresql.org/docs/9.4/static/indexes-unique.html
maggu

Tôi đồng tình với @ user1935361, nếu không thể tạo khóa ngoại thành một chỉ mục duy nhất (ít nhất là với PG 10) thì tôi đã gặp phải vấn đề này từ lâu rồi.
Andy

Câu trả lời:


131

Tôi đã có một số nghi ngờ về vấn đề cơ bản nhưng quan trọng này, vì vậy tôi quyết định tìm hiểu bằng ví dụ.

Hãy tạo chủ bảng thử nghiệm với hai cột, con_id với ràng buộc duy nhất và ind_id được lập chỉ mục bởi chỉ mục duy nhất.

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)

Trong mô tả bảng (\ d trong psql), bạn có thể cho biết ràng buộc duy nhất từ ​​chỉ mục duy nhất.

Độc đáo

Hãy kiểm tra tính độc đáo, chỉ trong trường hợp.

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

Nó hoạt động như mong đợi!

Khóa ngoại

Bây giờ chúng ta sẽ xác định chi tiết bảng với hai phím nước ngoài tham chiếu đến hai cột của chúng tôi trong tổng thể .

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)

Vâng, không có lỗi. Hãy chắc chắn rằng nó hoạt động.

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

Cả hai cột có thể được tham chiếu trong khóa ngoại.

Hạn chế sử dụng chỉ mục

Bạn có thể thêm ràng buộc bảng bằng chỉ mục duy nhất hiện có.

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)

Bây giờ không có sự khác biệt giữa mô tả ràng buộc cột.

Chỉ số một phần

Trong khai báo ràng buộc bảng, bạn không thể tạo các chỉ mục một phần. Nó đến trực tiếp từ định nghĩa của create table .... Trong khai báo chỉ mục duy nhất, bạn có thể thiết lập WHERE clauseđể tạo chỉ mục một phần. Bạn cũng có thể tạo chỉ mục trên biểu thức (không chỉ trên cột) và xác định một số tham số khác (đối chiếu, thứ tự sắp xếp, vị trí NULL).

Bạn không thể thêm ràng buộc bảng bằng chỉ mục một phần.

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.

nó là thông tin thực tế? đặc biệt là về chỉ số một phần
anatol

1
@anatol - vâng, đúng vậy.
klin

30

Một lợi thế nữa của việc sử dụng UNIQUE INDEXso với UNIQUE CONSTRAINTlà bạn có thể dễ dàng DROP/ CREATEchỉ mục CONCURRENTLY, trong khi với một ràng buộc bạn không thể.


4
AFAIK không thể bỏ đồng thời một chỉ mục duy nhất. postgresql.org/docs/9.3/static/sql-dropindex.html "Có một số lưu ý cần lưu ý khi sử dụng tùy chọn này. Chỉ có thể chỉ định một tên chỉ mục và tùy chọn CASCADE không được hỗ trợ. không thể bỏ được ràng buộc KHÓA CHÍNH XÁC hoặc CHÍNH HÃNG theo cách này.) "
Rafał Cieślak

15

Độc đáo là một hạn chế. Nó được thực hiện thông qua việc tạo ra một chỉ mục duy nhất vì một chỉ mục có thể nhanh chóng tìm kiếm tất cả các giá trị hiện có để xác định xem một giá trị đã cho đã tồn tại chưa.

Về mặt khái niệm, chỉ số là một chi tiết triển khai và tính duy nhất chỉ nên được liên kết với các ràng buộc.

Toàn văn

Vì vậy, hiệu suất tốc độ phải giống nhau


4

Một điều khác tôi đã gặp là bạn có thể sử dụng các biểu thức sql trong các chỉ mục duy nhất nhưng không bị ràng buộc.

Vì vậy, điều này không hoạt động:

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

nhưng làm việc sau đây.

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

Tôi sẽ sử dụng citextphần mở rộng.
ceving

@ceving nó phụ thuộc vào trường hợp sử dụng. đôi khi bạn muốn bảo quản vỏ trong khi vẫn đảm bảo tính độc đáo không phân biệt chữ hoa chữ thường
Sampson Crowley

2

Do nhiều người đã cung cấp các lợi thế của các chỉ mục duy nhất so với các ràng buộc duy nhất, nên đây là một nhược điểm: một ràng buộc duy nhất có thể được hoãn lại (chỉ được kiểm tra khi kết thúc giao dịch), không thể có một chỉ mục duy nhất.


Làm thế nào điều này có thể, cho rằng tất cả các ràng buộc duy nhất có một chỉ mục duy nhất?
Chris

1
Bởi vì các chỉ mục không có API để trì hoãn, chỉ có các ràng buộc thực hiện, do đó, trong khi bộ máy trì hoãn tồn tại dưới vỏ bọc để hỗ trợ các ràng buộc duy nhất, không có cách nào để tuyên bố một chỉ mục là có thể bảo vệ hoặc để trì hoãn nó.
Masklinn

0

Tôi đọc điều này trong tài liệu:

THÊM bảng_constraint [KHÔNG CÓ GIÁ TRỊ]

Biểu mẫu này thêm một ràng buộc mới vào một bảng bằng cách sử dụng cú pháp tương tự như CREATE TABLE, cộng với tùy chọn NOT VALID, hiện chỉ được phép cho các ràng buộc khóa ngoài. Nếu ràng buộc được đánh dấu NOT VALID, kiểm tra ban đầu có khả năng kéo dài để xác minh rằng tất cả các hàng trong bảng thỏa mãn ràng buộc được bỏ qua . Ràng buộc vẫn sẽ được thi hành đối với các lần chèn hoặc cập nhật tiếp theo (nghĩa là chúng sẽ thất bại trừ khi có một hàng khớp trong bảng được tham chiếu). Nhưng cơ sở dữ liệu sẽ không cho rằng ràng buộc giữ cho tất cả các hàng trong bảng, cho đến khi nó được xác thực bằng cách sử dụng tùy chọn VALIDATE CONSTRAINT.

Vì vậy, tôi nghĩ rằng đó là những gì bạn gọi là "tính duy nhất một phần" bằng cách thêm một ràng buộc.

Và, về cách đảm bảo tính độc đáo:

Thêm một ràng buộc duy nhất sẽ tự động tạo một chỉ mục cây B duy nhất trên cột hoặc nhóm cột được liệt kê trong ràng buộc. Một hạn chế duy nhất chỉ bao gồm một số hàng không thể được viết dưới dạng một ràng buộc duy nhất, nhưng có thể thực thi một hạn chế đó bằng cách tạo một chỉ mục một phần duy nhất.

Lưu ý: Cách ưa thích để thêm một ràng buộc duy nhất vào bảng là THAY ĐỔI THÊM THÊM THÊM THÊM. Việc sử dụng các chỉ mục để thực thi các ràng buộc duy nhất có thể được coi là một chi tiết triển khai không nên được truy cập trực tiếp. Tuy nhiên, cần lưu ý rằng không cần phải tạo các chỉ mục theo cách thủ công trên các cột duy nhất; làm như vậy sẽ chỉ nhân đôi chỉ mục được tạo tự động.

Vì vậy, chúng ta nên thêm ràng buộc, tạo ra một chỉ mục, để đảm bảo tính duy nhất.

Làm thế nào tôi nhìn thấy vấn đề này?

Một "hạn chế" nhằm mục đích gramatically đảm bảo rằng cột này phải là duy nhất, nó thiết lập một đạo luật, một quy tắc; trong khi "chỉ mục" là về ngữ nghĩa , về "cách thực hiện, làm thế nào để đạt được sự độc đáo, ý nghĩa duy nhất có nghĩa là gì khi thực hiện". Vì vậy, cách Postgresql thực hiện nó, rất hợp lý: đầu tiên, bạn tuyên bố rằng một cột phải là duy nhất, sau đó, Postgresql thêm việc thực hiện thêm một chỉ mục duy nhất cho bạn .


1
"Vì vậy, tôi nghĩ rằng đó là những gì bạn gọi là" tính duy nhất một phần "bằng cách thêm một ràng buộc." các chỉ mục chỉ có thể áp dụng cho một tập hợp con được xác định rõ của các bản ghi thông qua wheremệnh đề, vì vậy bạn có thể xác định rằng các bản ghi là IFF duy nhất chúng đáp ứng một số tiêu chí. Điều này chỉ đơn giản là vô hiệu hóa các ràng buộc cho một tập hợp các bản ghi không xác định trước các ràng buộc được tạo. Nó hoàn toàn khác biệt, và cái sau thì ít hữu ích hơn , mặc dù nó thuận tiện cho việc di chuyển tiến bộ tôi đoán.
Masklinn

0

Có một sự khác biệt trong khóa.
Thêm một chỉ mục không chặn truy cập đọc vào bảng.
Thêm một ràng buộc sẽ đặt khóa bảng (vì vậy tất cả các lựa chọn đều bị chặn) vì nó được thêm thông qua ALTER TABLE .


0

Một điều rất nhỏ có thể được thực hiện chỉ với các ràng buộc và không phải với các chỉ mục là sử dụng ON CONFLICT ON CONSTRAINTmệnh đề ( xem thêm câu hỏi này ).

Điều này không hoạt động:

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

Nó tạo ra:

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

Biến chỉ mục thành một ràng buộc:

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

INSERTtuyên bố bây giờ hoạt động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.