Restrição exclusiva do Postgres versus índice

157

Pelo que pude entender a documentação, as seguintes definições são equivalentes:

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

No entanto, uma observação no manual do Postgres 9.4 diz:

A maneira preferida de adicionar uma restrição exclusiva a uma tabela é ALTER TABLE ... ADD CONSTRAINT. O uso de índices para impor restrições exclusivas pode ser considerado um detalhe de implementação que não deve ser acessado diretamente.

(Editar: esta nota foi removida do manual com o Postgres 9.5.)

É apenas uma questão de bom estilo? Quais são as consequências práticas da escolha de uma dessas variantes (por exemplo, no desempenho)?

Adam Piotrowski
fonte
23
A (única) diferença prática é que você pode criar uma chave estrangeira para uma restrição exclusiva, mas não para um índice exclusivo.
A_horse_with_no_name
29
Uma vantagem do contrário ( como surgiu em outra pergunta recentemente ) é que você pode ter um índice exclusivo parcial , como "Única (foo) onde a barra é nula". AFAIK, não há como fazer isso com uma restrição.
IMSoP
3
@a_horse_with_no_name Não sei ao certo quando isso aconteceu, mas isso não parece mais ser verdade. Esse violino SQL permite referências de chave estrangeira a um índice exclusivo: sqlfiddle.com/#!17/20ee9 ; EDIT: adicionar um 'filtro' ao índice exclusivo faz com que ele pare de funcionar (conforme o esperado)
user1935361
1
da documentação do postgres: O PostgreSQL cria automaticamente um índice exclusivo quando uma restrição ou chave primária exclusiva é definida para uma tabela. postgresql.org/docs/9.4/static/indexes-unique.html
maggu
Concordo com o @ user1935361, se não fosse possível criar uma chave estrangeira para um índice exclusivo (pelo menos com a PG 10) eu já teria encontrado esse problema há muito tempo.
Andy

Respostas:

132

Eu tinha algumas dúvidas sobre esse problema básico, mas importante, então decidi aprender pelo exemplo.

Vamos criar o mestre da tabela de teste com duas colunas, con_id com restrição exclusiva e ind_id indexado por índice exclusivo.

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)

Na descrição da tabela (\ d em psql), você pode distinguir a restrição exclusiva do índice exclusivo.

Singularidade

Vamos verificar a exclusividade, apenas por precaução.

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

Funciona como esperado!

Chaves estrangeiras

Agora vamos definir a tabela de detalhes com duas chaves estrangeiras referenciando nossas duas colunas no mestre .

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)

Bem, sem erros. Vamos garantir que funcione.

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

Ambas as colunas podem ser referenciadas em chaves estrangeiras.

Restrição usando índice

Você pode adicionar restrições de tabela usando o índice exclusivo existente.

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)

Agora não há diferença entre a descrição das restrições da coluna.

Índices parciais

Na declaração de restrição de tabela, você não pode criar índices parciais. Vem diretamente da definição de create table .... Na declaração de índice exclusivo, você pode definir WHERE clausepara criar um índice parcial. Você também pode criar um índice na expressão (não apenas na coluna) e definir alguns outros parâmetros (agrupamento, ordem de classificação, posicionamento de NULLs).

Você não pode adicionar restrição de tabela usando índice parcial.

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
fonte
é informação real? especialmente sobre índices parciais
anatol 20/02/19
1
@anatol - sim, é.
Klin20:
30

Mais uma vantagem de usar UNIQUE INDEXvs. UNIQUE CONSTRAINTé que você pode facilmente DROP/ CREATEum índice CONCURRENTLY, enquanto que com uma restrição não pode.

Vadim Zingertal
fonte
4
AFAIK não é possível descartar simultaneamente um índice exclusivo. postgresql.org/docs/9.3/static/sql-dropindex.html "Há várias advertências a serem observadas ao usar esta opção. Somente um nome de índice pode ser especificado e a opção CASCADE não é suportada. (Portanto, um índice que suporta uma restrição UNIQUE ou PRIMARY KEY não pode ser descartada dessa maneira.) "
Rafał Cieślak
15

A exclusividade é uma restrição. É implementado por meio da criação de um índice exclusivo, pois um índice é capaz de pesquisar rapidamente todos os valores existentes para determinar se um determinado valor já existe.

Conceitualmente, o índice é um detalhe de implementação e a exclusividade deve ser associada apenas a restrições.

O texto completo

Portanto, o desempenho da velocidade deve ser o mesmo

Eugen Konkov
fonte
4

Outra coisa que encontrei é que você pode usar expressões sql em índices exclusivos, mas não em restrições.

Portanto, isso não funciona:

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

mas seguindo obras.

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));
김민준
fonte
Eu usaria a citextextensão.
ceving 11/03/19
@ceving depende do caso de uso. às vezes você quer preservar caixa, garantindo singularidade case-insensitive
Sampson Crowley
2

Como várias pessoas forneceram vantagens de índices exclusivos sobre restrições exclusivas, aqui está uma desvantagem: uma restrição exclusiva pode ser adiada (verificada apenas no final da transação), um índice exclusivo não pode ser.

Masklinn
fonte
Como pode ser isso, considerando que todas as restrições exclusivas têm um índice exclusivo?
Chris
1
Como os índices não têm uma API para adiar, apenas as restrições, por isso, embora o mecanismo de adiamento exista sob a cobertura para suportar restrições exclusivas, não há como declarar um índice como adiado ou adiado.
Masklinn
0

Eu li isso no documento:

ADICIONAR tabela_constraint [NOT VALID]

Este formulário adiciona uma nova restrição a uma tabela usando a mesma sintaxe que CREATE TABLE, além da opção NOT VALID, que atualmente é permitida apenas para restrições de chave estrangeira. Se a restrição estiver marcada NOT VALID, a verificação inicial potencialmente longa para verificar se todas as linhas da tabela atendem à restrição foi ignorada . A restrição ainda será aplicada a inserções ou atualizações subsequentes (ou seja, elas falharão, a menos que haja uma linha correspondente na tabela referenciada). Mas o banco de dados não assumirá que a restrição seja válida para todas as linhas da tabela, até que seja validada usando a opção VALIDATE CONSTRAINT.

Então, eu acho que é o que você chama de "exclusividade parcial", adicionando uma restrição.

E, sobre como garantir a exclusividade:

A adição de uma restrição exclusiva criará automaticamente um índice de árvore B exclusivo na coluna ou no grupo de colunas listadas na restrição. Uma restrição de exclusividade que cobre apenas algumas linhas não pode ser gravada como uma restrição exclusiva, mas é possível impor essa restrição criando um índice parcial exclusivo.

Nota: A maneira preferida de adicionar uma restrição exclusiva a uma tabela é ALTER TABLE… ADD CONSTRAINT. O uso de índices para impor restrições exclusivas pode ser considerado um detalhe de implementação que não deve ser acessado diretamente. No entanto, é preciso estar ciente de que não há necessidade de criar índices manualmente em colunas exclusivas; isso duplicaria o índice criado automaticamente.

Portanto, devemos adicionar restrições, o que cria um índice, para garantir a exclusividade.

Como vejo esse problema?

Uma "restrição" visa garantir gramaticalmente que esta coluna seja única, estabelece uma lei, uma regra; enquanto "índice" é semântico , sobre "como implementar, como alcançar a exclusividade, o que significa único quando se trata de implementação". Portanto, a maneira como o Postgresql a implementa é muito lógica: primeiro, você declara que uma coluna deve ser única; depois, o Postgresql adiciona a implementação da adição de um índice exclusivo para você .

WesternGun
fonte
1
"Então eu acho que é o que você chama de" exclusividade parcial "adicionando uma restrição". os índices podem se aplicar a apenas um subconjunto bem definido dos registros por meio da wherecláusula, para que você possa definir que os registros sejam IFF exclusivos e atendam a alguns critérios. Isso simplesmente desativa as restrições de um conjunto indefinido de registros que antecedem a restrição que está sendo criada. É completamente diferente, e o último é significativamente menos útil, embora seja conveniente para migrações progressivas, eu acho.
Masklinn
0

Há uma diferença no bloqueio.
A adição de um índice não bloqueia o acesso de leitura à tabela.
A adição de uma restrição coloca um bloqueio de tabela (para que todas as seleções sejam bloqueadas), uma vez que é adicionado via ALTER TABLE .

Bax
fonte
0

Uma coisa muito pequena que pode ser feita apenas com restrições e não com índices está usando a ON CONFLICT ON CONSTRAINTcláusula ( consulte também esta pergunta ).

Isso não funciona:

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

Produz:

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

Transforme o índice em uma restrição:

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

E a INSERTdeclaração agora funciona.

Lukas Eder
fonte