Por que o Postgres gera um valor PK já usado?


20

Estou usando o Django e, de vez em quando, recebo este erro:

IntegrityError: o valor duplicado da chave viola a restrição exclusiva "myapp_mymodel_pkey"
DETALHE: A chave (id) = (1) já existe.

De fato, meu banco de dados Postgres possui um objeto myapp_mymodel com a chave primária 1.

Por que o Postgres tentaria usar essa chave primária novamente? Ou, é mais provável que meu aplicativo (ou o ORM do Django) esteja causando isso?

Esse problema ocorreu mais três vezes seguidas agora. O que eu descobri é que quando isso acontece ocorrer acontece uma ou mais vezes em uma fila para uma determinada tabela, em seguida, de novo não. Parece ocorrer em todas as tabelas antes de parar completamente por dias, ocorrendo por pelo menos um minuto por tabela quando ocorre e ocorre apenas de forma intermitente (nem todas as tabelas imediatamente).

O fato de esse erro ser tão intermitente (aconteceu apenas três vezes em duas semanas - nenhuma outra carga no banco de dados, apenas eu testando meu aplicativo) é o que me deixa tão cauteloso com um problema de baixo nível.


O Django afirma especificamente que a chave primária é gerada pelo DBMS, a menos que seja especificado - agora, não sei o que @orokusaky estava fazendo em seu código python, mas acabei nesta página porque estou bastante confiante de que não tenho código tentando usar uma chave primária específica e nunca vi um DBMS tentando usar uma chave errada.
Mccc

Respostas:


34

O PostgreSQL não tentará inserir valores duplicados por si só, é você (sua aplicação, ORM incluído) quem o faz.

Pode ser uma sequência que alimenta os valores do PK definido na posição errada e a tabela já contém o valor igual ao seu nextval()- ou simplesmente que seu aplicativo faz a coisa errada. O primeiro é fácil de corrigir:

SELECT setval('your_sequence_name', (SELECT max(id) FROM your_table));

O segundo significa depuração.

O Django (ou qualquer outra estrutura popular) não redefine as seqüências por conta própria - caso contrário, teríamos perguntas semelhantes a cada dois dias.


Vale a pena notar (também com base na resposta de @ andi aqui) sobre os diferentes níveis de isolamento? Por exemplo, se a segunda consulta chegar antes que a primeira seja concluída, é possível, dado um cenário em que não estou usando transações, inserir um registro que resulte em obter max(id)antes da conclusão da primeira consulta e resultar em ambos o mesmo resultado?
Orokusaki #

5

Você provavelmente está tentando inserir uma linha em uma tabela para a qual o valor da sequência de colunas em série não é atualizado.

Considere a seguinte coluna na sua tabela, que é a chave primária definida pelo Django ORM para o postgres

id serial NOT NULL

Cujo valor padrão está definido como

nextval('table_name_id_seq'::regclass)

A sequência é avaliada apenas quando o campo de identificação é definido como em branco. Mas isso é problema se já houver entradas na tabela.

A pergunta é por que essas entradas anteriores não acionaram a atualização da sequência? Isso ocorre porque o valor do ID foi fornecido explicitamente para todas as entradas anteriores.

No meu caso, essas entradas iniciais foram carregadas de equipamentos através de migrações.

Esse problema também pode ser complicado através de entradas personalizadas com valor aleatório de PK.

Diga por exemplo. Existem 10 entradas na sua tabela. Você faz uma entrada explícita com PK = 15. As próximas quatro inserções no código funcionariam perfeitamente, mas a quinta criaria uma exceção.

DETAIL: Key (id)=(15) already exists.

Obrigado por este post. Estou depurando um caso como esse há muito tempo. Muito raramente isso ocorreu. Verificou-se que uma função administrativa "manual" específica poderia inserir IDs por conta própria, deixando o contador de identidade com um valor antigo. Este é um perigo real com "GERADO POR PADRÃO COMO IDENTIDADE". Pensarei duas vezes antes de usar "POR PADRÃO" em vez de "SEMPRE" na próxima vez que definir uma coluna de identidade.
Michael

4

Acabei aqui com o mesmo erro, que ocorria raramente e era difícil de rastrear, porque não estava procurando por onde deveria.

A falha foi a repetição de JS, que estava fazendo o POST no servidor duas vezes! Portanto, às vezes vale a pena dar uma olhada não apenas nas visualizações e formulários do seu django (ou em qualquer outra estrutura da web), mas também no que acontece na parte frontal.


1

Sim, coisa estranha. No meu caso, algo aparentemente errado durante o carregamento de dados nas migrações. Adicionei migração vazia e escrevi as linhas para adicionar alguns dados iniciais, 6 registros no meu caso.

db_alias = schema_editor.connection.alias
bulk = []
for item in items:
    bulk.append(MyModel(
        id=item[0],
        value=item[1],
        slug=item[2],
        name=item[3],
    ))

MyModel.objects.using(db_alias).bulk_create(bulk)

Então, no painel de administração, tentei adicionar um novo item e obtive:

Primeira tentativa:

DETAIL:  Key (id)=(1) already exists.

Tentativas posteriores:

DETAIL:  Key (id)=(2) already exists.
DETAIL:  Key (id)=(3) already exists.
DETAIL:  Key (id)=(4) already exists.
DETAIL:  Key (id)=(5) already exists.
DETAIL:  Key (id)=(6) already exists.

E, finalmente, o sétimo e tempos são todos bem-sucedidos

Estou dizendo que talvez algo relacionado a bulk_create tenha carregado 6 itens lá. Talvez seja algo semelhante no seu projeto Django causando isso.

Django 1.9 PostgreSQL 9.3.14

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.