Perché Postgres genera un valore PK già utilizzato?


20

Sto usando Django e ogni tanto ottengo questo errore:

IntegrityError: il valore chiave duplicato viola il vincolo univoco "myapp_mymodel_pkey"
DETAIL: Key (id) = (1) esiste già.

Il mio database Postgres infatti ha un oggetto myapp_mymodel con la chiave primaria di 1.

Perché Postgres dovrebbe tentare di riutilizzare quella chiave primaria? Oppure, è molto probabile che questa sia la mia applicazione (o l'ORM di Django) a causare questo?

Questo problema si è verificato altre 3 volte di seguito proprio ora. Quello che ho trovato è che quando lo fa verifichi accade una o più volte di fila per una determinata tabella, quindi non di nuovo. Sembra accadere per ogni tavolo prima che si fermi completamente per giorni, accadendo per almeno un minuto per tavolo quando si verifica, e solo in modo intermittente (non tutti i tavoli immediatamente).

Il fatto che questo errore sia così intermittente (è accaduto solo 3 o più volte in 2 settimane - nessun altro carico sul DB, solo io testando la mia applicazione) è ciò che mi rende così diffidente nei confronti di un problema di basso livello.


Django afferma specificamente che la chiave primaria è generata dal DBMS se non specificato - ora, non so cosa stesse facendo @orokusaky nel suo codice python, ma sono finito su questa pagina perché sono abbastanza sicuro di non avere codice cercando di usare una chiave primaria specifica e non ho mai visto un DBMS tentare di usare una chiave sbagliata.
mccc,

Risposte:


34

PostgreSQL non proverà a inserire valori duplicati da solo, sei tu (la tua applicazione, ORM incluso) che lo fa.

Può essere una sequenza che fornisce i valori al set PK nella posizione sbagliata e che la tabella contiene già un valore uguale al suo nextval()- o semplicemente che l'applicazione fa la cosa sbagliata. Il primo è facile da risolvere:

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

Il secondo significa debug.

Django (o qualsiasi altro framework popolare) non reimposta le sequenze da solo - altrimenti avremmo domande simili a giorni alterni.


Vale la pena notare (basato anche sulla risposta di @ andi qui) sui diversi livelli di isolamento? Ad esempio, se la seconda query arriva prima che la prima sia completata, è possibile, dato uno scenario in cui non sto utilizzando le transazioni, inserire un record che si ottenga max(id)prima del completamento della prima query e quindi avere entrambi lo stesso risultato?
Orokusaki,

5

Molto probabilmente stai cercando di inserire una riga in una tabella per la quale il valore della sequenza della colonna seriale non viene aggiornato.

Prendi in considerazione la seguente colonna nella tabella che è la chiave primaria definita da Django ORM per postgres

id serial NOT NULL

Il cui valore predefinito è impostato su

nextval('table_name_id_seq'::regclass)

La sequenza viene valutata solo quando il campo ID è impostato come vuoto. Ma questo è un problema se ci sono già voci nella tabella.

La domanda è: perché quelle voci precedenti non hanno attivato l'aggiornamento della sequenza? Questo perché il valore ID è stato fornito esplicitamente per tutte le voci precedenti.

Nel mio caso, le voci iniziali sono state caricate dagli infissi attraverso le migrazioni.

Questo problema può anche diventare complicato tramite voci personalizzate con valore PK casuale.

Dì per es. Ci sono 10 voci nella tua tabella. Si effettua una voce esplicita con PK = 15. I successivi quattro inserimenti tramite codice funzionerebbero perfettamente, ma il quinto solleverebbe un'eccezione.

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

Grazie per questo post. Ho eseguito il debug di un caso come questo per molto tempo. Si è verificato molto raramente. Si è scoperto che una specifica funzione di amministrazione "manuale" poteva inserire gli ID da solo, lasciando il contatore di identità con un vecchio valore. Questo è un vero pericolo con "GENERATO DA DEFAULT COME IDENTITÀ". Ci penserò due volte prima di usare "BY DEFAULT" invece di "SEMPRE" la prossima volta che definirò una colonna di identità.
Michael,

4

Sono finito qui con lo stesso errore, che si stava verificando raramente, ed era difficile da rintracciare, perché lo cercavo non dove avrei dovuto.

L'errore era la ripetizione di JS che faceva il POST al server due volte! Quindi a volte vale la pena dare un'occhiata non solo alle viste e ai moduli di django (o di qualsiasi altro framework web), ma anche a ciò che accade sul lato frontale.


1

Sì, cosa strana. Nel mio caso apparentemente qualcosa di sbagliato durante il caricamento dei dati nelle migrazioni. Ho aggiunto la migrazione vuota e ho scritto le righe per aggiungere alcuni dati iniziali, 6 record nel mio 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)

Quindi nel pannello di amministrazione ho provato ad aggiungere un nuovo elemento e ho ottenuto:

Primo tentativo:

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

Tentativi successivi:

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 il 7 ° e nei tempi hanno tutti successo

Quindi sto dicendo che forse c'è qualcosa in relazione a bulk_create mentre ho caricato 6 elementi lì. Potrebbe essere qualcosa di simile nel tuo progetto Django a causarlo.

Django 1.9 PostgreSQL 9.3.14

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.