Mengapa Postgres menghasilkan nilai PK yang sudah digunakan?

20

Saya menggunakan Django, dan sesekali saya mendapatkan kesalahan ini:

IntegrityError: nilai kunci duplikat melanggar batasan unik "myapp_mymodel_pkey"
DETAIL: Kunci (id) = (1) sudah ada.

Database Postgres saya sebenarnya memiliki objek myapp_mymodel dengan kunci utama 1.

Mengapa Postgres mencoba menggunakan kunci utama itu lagi? Atau, apakah ini kemungkinan besar aplikasi saya (atau ORANG Django) yang menyebabkan ini?

Masalah ini terjadi 3 kali berturut-turut sekarang. Apa yang saya temukan adalah bahwa ketika tidak terjadi hal itu terjadi satu kali atau lebih berturut-turut untuk tabel tertentu, kemudian tidak lagi. Tampaknya terjadi untuk setiap tabel sebelum benar-benar berhenti selama berhari-hari, terjadi selama setidaknya satu menit atau lebih per tabel ketika itu terjadi, dan hanya terjadi sebentar-sebentar (tidak semua tabel segera).

Fakta bahwa kesalahan ini sangat terputus-putus (terjadi hanya 3 kali atau lebih dalam 2 minggu - tidak ada beban lain pada DB, hanya saya menguji aplikasi saya) adalah apa yang membuat saya sangat waspada terhadap masalah tingkat rendah.

orokusaki
sumber
Django secara khusus menyatakan bahwa kunci primer dihasilkan oleh DBMS kecuali ditentukan - sekarang, saya tidak tahu apa yang dilakukan @orokusaky dalam kode python-nya, tetapi saya berakhir di halaman ini karena saya cukup yakin bahwa saya tidak memiliki kode mencoba menggunakan kunci primer spesifik dan saya belum pernah melihat DBMS mencoba menggunakan yang salah.
mccc

Jawaban:

34

PostgreSQL tidak akan mencoba untuk memasukkan nilai duplikat sendiri, itu adalah Anda (aplikasi Anda, termasuk ORM) yang melakukannya.

Ini bisa berupa urutan yang memberi nilai ke PK yang disetel ke posisi yang salah dan tabel sudah berisi nilai yang sama dengan itu nextval()- atau hanya bahwa aplikasi Anda melakukan hal yang salah. Yang pertama mudah diperbaiki:

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

Yang kedua berarti debugging.

Django (atau kerangka kerja populer lainnya) tidak mengatur ulang urutannya sendiri - jika tidak kita akan memiliki pertanyaan serupa setiap hari.

dezso
sumber
Apakah perlu dicatat (juga berdasarkan jawaban @ andi di sini) tentang tingkat isolasi yang berbeda? Misalnya, jika kueri kedua masuk sebelum yang pertama selesai, apakah mungkin, mengingat skenario di mana saya tidak menggunakan transaksi, masukkan catatan yang menghasilkan max(id)sebelum kueri pertama selesai, dan kemudian menghasilkan keduanya memiliki hasil yang sama?
orokusaki
5

Anda kemungkinan besar mengikat untuk memasukkan baris dalam tabel yang nilai urutan kolom serialnya tidak diperbarui.

Pertimbangkan kolom berikut di tabel Anda yang merupakan kunci utama yang didefinisikan oleh Django ORM untuk postgres

id serial NOT NULL

Nilai default siapa yang diatur

nextval('table_name_id_seq'::regclass)

Urutan hanya dievaluasi ketika bidang id disetel sebagai kosong. Tapi itu masalah jika sudah ada entri ke dalam tabel.

Pertanyaannya adalah mengapa entri sebelumnya tidak memicu pembaruan urutan? Itu karena nilai id secara eksplisit disediakan untuk semua entri sebelumnya.

Dalam kasus saya, entri awal dimuat dari perlengkapan melalui migrasi.

Masalah ini juga dapat menjadi rumit melalui entri khusus dengan nilai PK acak.

Katakan misalnya. Ada 10 entri ke meja Anda. Anda membuat entri eksplisit dengan PK = 15. Empat sisipan berikutnya melalui kode akan bekerja dengan baik tetapi yang ke-5 akan memunculkan pengecualian.

DETAIL: Key (id)=(15) already exists.
Abhishek
sumber
Terima kasih untuk posting ini. Saya telah men-debug kasus seperti ini untuk waktu yang lama. Sangat jarang hal itu terjadi. Ternyata fungsi admin "manual" tertentu dapat menyisipkan id sendiri, meninggalkan penghitung identitas dengan nilai lama. Ini adalah bahaya nyata dengan "DIHASILKAN OLEH DEFAULT AS IDENTITY". Saya akan berpikir dua kali sebelum menggunakan "BY DEFAULT" dan bukannya "SELALU" pada saat saya mendefinisikan kolom identitas.
Michael
4

Saya berakhir di sini dengan kesalahan yang sama, yang jarang terjadi, dan sulit dilacak, karena saya tidak mencarinya di tempat yang seharusnya.

Kesalahan adalah pengulangan JS yang melakukan POST ke server dua kali! Jadi kadang-kadang patut untuk melihat tidak hanya pada django Anda (atau kerangka kerja web lainnya) tetapi juga apa yang terjadi di sisi paling depan.

andilab
sumber
1

Ya hal yang aneh. Dalam kasus saya, ada sesuatu yang salah ketika memuat data dalam migrasi. Saya menambahkan migrasi kosong dan menulis baris untuk menambahkan beberapa data awal, 6 catatan dalam kasus saya.

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)

Kemudian di panel admin saya mencoba menambahkan item baru dan mendapatkan:

Percobaan pertama:

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

Upaya selanjutnya:

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.

Dan akhirnya yang ke 7 dan seterusnya semuanya sukses

Jadi saya katakan mungkin ada sesuatu yang berhubungan dengan bulk_create ketika saya memuat 6 item di sana. Ini mungkin sesuatu yang serupa dalam proyek Django Anda yang menyebabkan hal itu.

Django 1.9 PostgreSQL 9.3.14

Bartosz Dabrowski
sumber