Batasan unik vs indeks Postgres

157

Karena saya dapat memahami dokumentasi , definisi berikut ini setara:

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

Namun, catatan dalam manual untuk Postgres 9.4 mengatakan:

Cara yang disukai untuk menambahkan batasan unik ke tabel adalah ALTER TABLE ... ADD CONSTRAINT. Penggunaan indeks untuk menegakkan batasan unik dapat dianggap sebagai detail implementasi yang tidak boleh diakses secara langsung.

(Edit: catatan ini telah dihapus dari manual dengan Postgres 9.5.)

Apakah ini hanya masalah gaya yang baik? Apa konsekuensi praktis dari pilihan salah satu varian ini (misalnya dalam kinerja)?

Adam Piotrowski
sumber
23
Perbedaan praktis (hanya) adalah Anda dapat membuat kunci asing untuk batasan unik tetapi tidak ke indeks unik.
a_horse_with_no_name
29
Keuntungan sebaliknya ( seperti yang muncul dalam pertanyaan lain baru-baru ini ) adalah bahwa Anda dapat memiliki indeks unik parsial , seperti "Unik (foo) Di mana bar Is Null". AFAIK, tidak ada cara untuk melakukan itu dengan kendala.
IMSoP
3
@a_horse_with_no_name Saya tidak yakin kapan ini terjadi, tetapi ini tampaknya tidak lagi benar. Biola SQL ini memungkinkan referensi kunci asing ke indeks unik: sqlfiddle.com/#!17/20ee9 ; EDIT: menambahkan 'filter' ke indeks unik menyebabkan ini berhenti berfungsi (seperti yang diharapkan)
user1935361
1
dari dokumentasi postgres: PostgreSQL secara otomatis membuat indeks unik ketika batasan unik atau kunci utama didefinisikan untuk sebuah tabel. postgresql.org/docs/9.4/static/indexes-unique.html
maggu
Saya setuju dengan @ user1935361, jika tidak mungkin untuk membuat kunci asing ke indeks yang unik (setidaknya dengan PG 10) saya akan mengalami masalah ini sejak lama.
Andy

Jawaban:

132

Saya memiliki keraguan tentang masalah mendasar namun penting ini, jadi saya memutuskan untuk belajar dengan memberi contoh.

Mari kita buat master tabel uji dengan dua kolom, con_id dengan kendala unik dan ind_id diindeks oleh indeks unik.

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)

Dalam deskripsi tabel (\ d dalam psql), Anda dapat memberi tahu batasan unik dari indeks unik.

Keunikan

Mari kita periksa keunikannya, untuk berjaga-jaga.

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

Ini berfungsi seperti yang diharapkan!

Kunci asing

Sekarang kita akan mendefinisikan tabel detail dengan dua kunci asing yang merujuk ke dua kolom kita di 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)

Yah, tidak ada kesalahan. Mari kita pastikan itu berhasil.

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

Kedua kolom dapat direferensikan dalam kunci asing.

Batasi menggunakan indeks

Anda bisa menambahkan batasan tabel menggunakan indeks unik yang ada.

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)

Sekarang tidak ada perbedaan antara deskripsi batasan kolom.

Indeks sebagian

Dalam deklarasi kendala tabel Anda tidak dapat membuat indeks parsial. Ini datang langsung dari definisi dari create table .... Dalam deklarasi indeks unik Anda dapat mengatur WHERE clauseuntuk membuat indeks parsial. Anda juga dapat membuat indeks pada ekspresi (tidak hanya pada kolom) dan menentukan beberapa parameter lainnya (susunan, susunan urutan, penempatan NULLs).

Anda tidak bisa menambahkan batasan tabel menggunakan indeks parsial.

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.
klin
sumber
apakah ini info aktual? terutama tentang indeks parsial
anatol
1
@anatol - ya, benar.
klin
30

Satu lagi keuntungan menggunakan UNIQUE INDEXvs. UNIQUE CONSTRAINTadalah bahwa Anda dapat dengan mudah DROP/ CREATEindeks CONCURRENTLY, sedangkan dengan kendala Anda tidak bisa.

Vadim Zingertal
sumber
4
AFAIK tidak mungkin untuk menjatuhkan indeks unik secara bersamaan. postgresql.org/docs/9.3/static/sql-dropindex.html "Ada beberapa peringatan yang harus diperhatikan ketika menggunakan opsi ini. Hanya satu nama indeks yang dapat ditentukan, dan opsi CASCADE tidak didukung. (Dengan demikian, indeks yang mendukung kendala UNIK atau PRIMARY KEY tidak dapat dijatuhkan dengan cara ini.) "
Rafał Cieślak
15

Keunikan adalah kendala. Itu terjadi diimplementasikan melalui pembuatan indeks unik karena indeks dengan cepat dapat mencari semua nilai yang ada untuk menentukan apakah nilai yang diberikan sudah ada.

Secara konseptual indeks adalah detail implementasi dan keunikan harus dikaitkan hanya dengan kendala.

Teks lengkap

Jadi kinerja kecepatan harus sama

Eugen Konkov
sumber
4

Hal lain yang saya temui adalah bahwa Anda dapat menggunakan ekspresi sql dalam indeks unik tetapi tidak dalam kendala.

Jadi, ini tidak berfungsi:

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

tetapi mengikuti karya.

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));
김민준
sumber
Saya akan menggunakan citextekstensi.
ceving
@veving tergantung pada use case. kadang-kadang Anda ingin melindungi casing sambil memastikan keunikan case-insensitive
Sampson Crowley
2

Karena berbagai orang telah memberikan keuntungan dari indeks unik daripada kendala unik, inilah kelemahannya: kendala unik dapat ditangguhkan (hanya diperiksa pada akhir transaksi), indeks unik tidak dapat.

Masklinn
sumber
Bagaimana ini bisa terjadi, mengingat bahwa semua kendala unik memiliki indeks yang unik?
Chris
1
Karena indeks tidak memiliki API untuk menunda, hanya kendala yang melakukannya, jadi sementara mesin penangguhan ada di bawah penutup untuk mendukung kendala unik, tidak ada cara untuk menyatakan indeks sebagai ditangguhkan, atau untuk menundanya.
Masklinn
0

Saya membaca ini di dokumen:

ADD table_constraint [BUKAN VALID]

Formulir ini menambahkan kendala baru ke tabel menggunakan sintaksis yang sama seperti CREATE TABLE, ditambah opsi NOT VALID, yang saat ini hanya diperbolehkan untuk batasan kunci asing. Jika kendala ditandai NOT VALID, pemeriksaan awal yang berpotensi panjang untuk memverifikasi bahwa semua baris dalam tabel memenuhi batasan dilewati . Kendala masih akan diberlakukan terhadap sisipan atau pembaruan berikutnya (yaitu, mereka akan gagal kecuali jika ada baris yang cocok di tabel yang direferensikan). Namun basis data tidak akan menganggap bahwa kendala berlaku untuk semua baris dalam tabel, sampai divalidasi dengan menggunakan opsi VALIDATE CONSTRAINT.

Jadi saya pikir itu adalah apa yang Anda sebut "keunikan parsial" dengan menambahkan kendala.

Dan, tentang cara memastikan keunikan:

Menambahkan kendala unik akan secara otomatis membuat indeks B-tree unik pada kolom atau grup kolom yang tercantum dalam kendala. Pembatasan keunikan yang hanya mencakup beberapa baris tidak dapat ditulis sebagai batasan unik, tetapi dimungkinkan untuk menerapkan pembatasan semacam itu dengan membuat indeks parsial unik.

Catatan: Cara yang disukai untuk menambahkan batasan unik ke tabel adalah ALTER TABLE ... ADD CONSTRAINT. Penggunaan indeks untuk menegakkan batasan unik dapat dianggap sebagai detail implementasi yang tidak boleh diakses secara langsung. Namun, harus diperhatikan bahwa tidak perlu membuat indeks secara manual pada kolom unik; hal itu hanya akan menggandakan indeks yang dibuat secara otomatis.

Jadi kita harus menambahkan batasan, yang menciptakan indeks, untuk memastikan keunikan.

Bagaimana saya melihat masalah ini?

Sebuah "kendala" bertujuan untuk gramatically memastikan bahwa kolom ini harus unik, itu menetapkan hukum, aturan; sementara "indeks" bersifat semantik , tentang "bagaimana menerapkan, bagaimana mencapai keunikan, apa artinya unik ketika datang ke implementasi". Jadi, cara Postgresql mengimplementasikannya, sangat logis: pertama, Anda menyatakan bahwa sebuah kolom harus unik, kemudian, Postgresql menambahkan penerapan penambahan indeks unik untuk Anda .

WesternGun
sumber
1
"Jadi saya pikir ini adalah apa yang Anda sebut" keunikan parsial "dengan menambahkan batasan." indeks hanya dapat diterapkan untuk subset yang didefinisikan dengan baik melalui whereklausa, sehingga Anda dapat mendefinisikan bahwa catatan adalah IFF unik yang memenuhi beberapa kriteria. Ini hanya menonaktifkan batasan untuk set rekaman yang tidak ditentukan yang mendahului kendala yang sedang dibuat. Ini benar-benar berbeda, dan yang terakhir ini secara signifikan kurang berguna, walaupun saya rasa nyaman untuk migrasi progresif.
Masklinn
0

Ada perbedaan dalam penguncian.
Menambahkan indeks tidak memblokir akses baca ke tabel.
Menambahkan batasan tidak menempatkan kunci tabel (sehingga semua pilihan diblokir) karena ditambahkan melalui ALTER TABLE .

Bax
sumber
0

Suatu hal yang sangat kecil yang dapat dilakukan dengan kendala saja dan tidak dengan indeks menggunakan ON CONFLICT ON CONSTRAINTklausa ( lihat juga pertanyaan ini ).

Ini tidak berfungsi:

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

Itu menghasilkan:

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

Ubah indeks menjadi batasan:

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

Dan INSERTpernyataan itu sekarang berfungsi.

Lukas Eder
sumber