Warum generiert Postgres einen bereits verwendeten PK-Wert?

19

Ich benutze Django und bekomme ab und zu den folgenden Fehler:

IntegrityError: doppelter Schlüsselwert verletzt eindeutige Einschränkung "myapp_mymodel_pkey"
DETAIL: Schlüssel (id) = (1) ist bereits vorhanden.

Meine Postgres-Datenbank hat tatsächlich ein myapp_mymodel- Objekt mit dem Primärschlüssel 1.

Warum sollte Postgres versuchen, diesen Primärschlüssel erneut zu verwenden? Oder verursacht dies höchstwahrscheinlich meine Anwendung (oder Djangos ORM)?

Dieses Problem trat gerade noch dreimal hintereinander auf. Was ich gefunden habe ist , dass , wenn es nicht auftreten , es geschieht ein oder mehr Male in einer Reihe für eine bestimmte Tabelle, dann nicht wieder. Es scheint für jeden Tisch zu passieren, bevor er tagelang vollständig angehalten wird, und zwar für mindestens eine Minute oder so pro Tisch, wenn es auftritt, und nur zeitweise (nicht alle Tische sofort).

Die Tatsache, dass dieser Fehler so sporadisch auftritt (nur etwa drei Mal in zwei Wochen aufgetreten - keine andere Belastung der Datenbank, nur ich teste meine Anwendung), macht mich bei einem Problem auf niedriger Ebene so vorsichtig.

orokusaki
quelle
Django gibt ausdrücklich an, dass der Primärschlüssel vom DBMS generiert wird, sofern nicht anders angegeben. Jetzt weiß ich nicht, was @orokusaky in seinem Python-Code getan hat, aber ich bin auf dieser Seite gelandet, weil ich ziemlich sicher bin, dass ich keinen Code habe Beim Versuch, einen bestimmten Primärschlüssel zu verwenden, habe ich noch nie ein DBMS gesehen, das versucht, einen falschen zu verwenden.
mccc

Antworten:

33

PostgreSQL versucht nicht, doppelte Werte selbst einzufügen, sondern Sie (Ihre Anwendung, einschließlich ORM).

Es kann sich entweder um eine Sequenz handeln, die dem PK die Werte zuführt, die an der falschen Position eingestellt sind, und die Tabelle, die bereits den gleichen Wert enthält nextval()- oder einfach darum, dass Ihre Anwendung das Falsche tut. Das erste Problem lässt sich leicht beheben:

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

Das zweite bedeutet Debuggen.

Django (oder ein beliebtes anderes Framework) setzt Sequenzen nicht von alleine zurück - sonst hätten wir jeden zweiten Tag ähnliche Fragen.

dezso
quelle
Lohnt es sich, (auch basierend auf der Antwort von @ andi) auf die verschiedenen Isolationsstufen hinzuweisen? Wenn zum Beispiel die zweite Abfrage eingeht, bevor die erste abgeschlossen ist, ist es in einem Szenario, in dem ich keine Transaktionen verwende, möglich, einen Datensatz einzufügen, der dazu führt, dass max(id)vor Abschluss der ersten Abfrage abgerufen wird, und dann beide ausgeführt werden das gleiche Ergebnis?
Orokusaki
5

Sie werden höchstwahrscheinlich eine Zeile in eine Tabelle einfügen, für die der Wert der seriellen Spaltensequenz nicht aktualisiert wird.

Betrachten Sie die folgende Spalte in Ihrer Tabelle, die der von Django ORM für Postgres definierte Primärschlüssel ist

id serial NOT NULL

Dessen Standardwert ist auf festgelegt

nextval('table_name_id_seq'::regclass)

Die Reihenfolge wird nur ausgewertet, wenn das Feld id leer ist. Dies ist jedoch ein Problem, wenn bereits Einträge in der Tabelle vorhanden sind.

Die Frage ist, warum diese früheren Einträge keine Sequenzaktualisierung ausgelöst haben. Dies liegt daran, dass der ID-Wert für alle früheren Einträge explizit angegeben wurde.

In meinem Fall wurden diese anfänglichen Einträge von Fixtures durch Migrationen geladen.

Dieses Problem kann auch durch benutzerdefinierte Einträge mit zufälligen PK-Werten schwierig werden.

Sprich für zB. Ihre Tabelle enthält 10 Einträge. Sie machen eine explizite Eingabe mit PK = 15. Die nächsten vier Code-Einfügungen würden einwandfrei funktionieren, die fünfte würde jedoch eine Ausnahme auslösen.

DETAIL: Key (id)=(15) already exists.
Abhishek
quelle
Vielen Dank für diesen Beitrag. Ich habe einen Fall wie diesen schon lange getestet. Sehr selten ist es vorgekommen. Es stellte sich heraus, dass eine bestimmte "manuelle" Admin-Funktion IDs selbstständig einfügen und den Identitätszähler mit einem alten Wert belassen konnte. Dies ist eine echte Gefahr mit "GENERATED BY DEFAULT AS IDENTITY". Ich werde zweimal überlegen, bevor ich beim nächsten Definieren einer Identitätsspalte "BY DEFAULT" anstelle von "IMMER" verwende.
Michael,
4

Ich landete hier mit genau demselben Fehler, der selten auftrat und schwer zu verfolgen war, weil ich nicht dort nach ihm suchte, wo ich sollte.

Fehler war die JS-Wiederholung, bei der der POST zweimal auf dem Server ausgeführt wurde! Manchmal lohnt es sich also, nicht nur Ihre Django-Ansichten und -Formulare (oder andere Webframeworks) zu betrachten, sondern auch, was ganz vorne passiert.

andilabs
quelle
1

Ja, komische Sache. In meinem Fall scheint etwas beim Laden von Daten bei Migrationen nicht zu stimmen. Ich fügte leere Migration hinzu und schrieb die Zeilen, um einige anfängliche Daten hinzuzufügen, 6 Aufzeichnungen in meinem Fall.

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)

Dann habe ich im Admin-Bereich versucht, ein neues Element hinzuzufügen und Folgendes erhalten:

Erster Versuch:

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

Spätere Versuche:

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.

Und schließlich sind die siebten und letzten Male alle erfolgreich

Ich sage also, dass es vielleicht etwas mit bulk_create zu tun hat, als ich dort 6 Artikel geladen habe. Vielleicht ist es etwas Ähnliches in Ihrem Django-Projekt, das das verursacht.

Django 1.9 PostgreSQL 9.3.14

Bartosz Dabrowski
quelle