Dlaczego Postgres generuje już używaną wartość PK?

22

Używam Django i od czasu do czasu pojawia się ten błąd:

IntegrityError: zduplikowana wartość klucza narusza unikalne ograniczenie „klucz_aplikacji_aplikacji”
SZCZEGÓŁ: Klucz (identyfikator) = (1) już istnieje.

Moja baza danych Postgres faktycznie ma obiekt myapp_mymodel z kluczem podstawowym 1.

Dlaczego Postgres ponownie próbowałby użyć tego klucza podstawowego? A może jest to prawdopodobnie spowodowane przez moją aplikację (lub ORM Django)?

Ten problem występował jeszcze 3 razy z rzędu. Co znalazłem to, że kiedy ma nastąpić zdarza się jeden lub więcej razy z rzędu dla danej tabeli, a następnie nie ponownie. Wydaje się, że dzieje się to na każdym stole, zanim całkowicie zatrzyma się na kilka dni, dzieje się przez co najmniej minutę na stole, kiedy to nastąpi, i dzieje się tylko z przerwami (nie wszystkie stoły od razu).

Fakt, że ten błąd jest tak nieregularny (zdarzało się tylko 3 razy w ciągu 2 tygodni - nie ma żadnego obciążenia DB, tylko ja testuję moją aplikację) sprawia, że ​​jestem tak ostrożny wobec problemu niskiego poziomu.

orokusaki
źródło
Django wyraźnie stwierdza, że klucz podstawowy jest generowany przez DBMS, chyba że określono inaczej - teraz nie wiem, co @orokusaky robił w swoim kodzie Pythona, ale znalazłem się na tej stronie, ponieważ jestem pewien, że nie mam kodu próbuję użyć określonego klucza podstawowego i nigdy nie widziałem DBMS próbującego użyć niewłaściwego klucza.
mccc,

Odpowiedzi:

34

PostgreSQL nie będzie próbował wstawiać zduplikowanych wartości samodzielnie, tylko Ty (Twoja aplikacja, w tym ORM).

Może to być sekwencja dostarczająca wartości do zestawu PK do niewłaściwej pozycji, a tabela już zawiera wartość równą jej nextval()- lub po prostu, że twoja aplikacja robi coś złego. Pierwszy jest łatwy do naprawienia:

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

Drugi oznacza debugowanie.

Django (lub jakikolwiek inny popularny framework) nie resetuje sekwencji samodzielnie - w przeciwnym razie mielibyśmy podobne pytania co drugi dzień.

dezso
źródło
Czy warto zauważyć (również w oparciu o odpowiedź @ andi tutaj) o różnych poziomach izolacji? Na przykład, jeśli drugie zapytanie pojawi się przed zakończeniem pierwszego, czy jest możliwe, biorąc pod uwagę scenariusz, w którym nie korzystam z transakcji, wstawić rekord, który powoduje uzyskanie max(id)przed zakończeniem pierwszego zapytania, a następnie powoduje, że oba mają ten sam wynik?
orokusaki,
7

Najprawdopodobniej chcesz wprowadzić wiersz do tabeli, dla której wartość sekwencji kolumny szeregowej nie jest aktualizowana.

Rozważ następującą kolumnę w tabeli, która jest kluczem podstawowym zdefiniowanym przez Django ORM dla postgres

id serial NOT NULL

Czyją wartością domyślną jest

nextval('table_name_id_seq'::regclass)

Sekwencja jest oceniana tylko wtedy, gdy pole id jest ustawione jako puste. Jest to problem, jeśli w tabeli są już wpisy.

Pytanie, dlaczego te wcześniejsze wpisy nie spowodowały aktualizacji sekwencji? Wynika to z faktu, że wartość id została jawnie podana dla wszystkich wcześniejszych wpisów.

W moim przypadku te początkowe wpisy zostały załadowane z urządzeń poprzez migracje.

Ten problem może być również trudny dzięki niestandardowym wpisom z losową wartością PK.

Powiedz na przykład W Twojej tabeli znajduje się 10 pozycji. Dokonujesz wyraźnego wpisu z PK = 15. Następne cztery wstawki w kodzie działałyby idealnie, ale piąta spowodowałaby wyjątek.

DETAIL: Key (id)=(15) already exists.
Abhishek
źródło
Dziękuję za ten post. Debuguję taki przypadek od dłuższego czasu. Bardzo rzadko się to zdarzało. Okazało się, że konkretna „ręczna” funkcja administracyjna może sama wstawiać identyfikatory, pozostawiając licznik tożsamości ze starą wartością. To jest prawdziwe niebezpieczeństwo w przypadku „WYTWARZANE PRZEZ DOMYŚLNĄ JAKOŚĆ TOŻSAMOŚCI”. Zastanowię się dwa razy, zanim użyję „BY DOMYŚLNIE” zamiast „ZAWSZE”, kiedy następnym razem zdefiniuję kolumnę tożsamości.
Michael
4

Skończyło się na mnie z tym samym błędem, który zdarzał się rzadko i był trudny do wyśledzenia, ponieważ szukałem go nie tam, gdzie powinienem.

Usterką było powtórzenie JS, które dwukrotnie wykonało test POST na serwerze! Czasami więc warto przyjrzeć się nie tylko widokom i formularzom django (lub dowolnego innego frameworka), ale także temu, co dzieje się na pierwszej stronie.

andilabs
źródło
1

Tak, dziwna rzecz. W moim przypadku coś najwyraźniej jest błędne podczas ładowania danych w migracji. Dodałem pustą migrację i napisałem wiersze, aby dodać trochę danych początkowych, w moim przypadku 6 rekordów .

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)

Następnie w panelu administracyjnym próbowałem dodać nowy element i otrzymałem:

Pierwsze podejscie:

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

Późniejsze próby:

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.

I wreszcie 7. i na czas wszystkie są udane

Mówię więc, że może coś związanego z bulk_create, gdy załadowałem tam 6 elementów. Może to być przyczyną podobnego problemu w projekcie Django.

Django 1.9 PostgreSQL 9.3.14

Bartosz Dąbrowski
źródło