เหตุใด Postgres จึงสร้างค่า PK ที่ใช้ไปแล้ว


20

ฉันใช้ Django และทุกครั้งที่ฉันได้รับข้อผิดพลาด:

IntegrityError: ค่าคีย์ที่ซ้ำกันละเมิดข้อ จำกัด ที่ไม่ซ้ำกัน "myapp_mymodel_pkey" ราย
ละเอียด: Key (id) = (1) มีอยู่แล้ว

จริง ๆ แล้วฐานข้อมูล Postgres ของฉันมีวัตถุmyapp_mymodel ที่มีคีย์หลักเป็น 1

เหตุใด Postgres จะพยายามใช้คีย์หลักนั้นอีกครั้ง หรือนี่เป็นไปได้มากว่าแอปพลิเคชันของฉัน (หรือ ORM ของ Django) เป็นสาเหตุให้เกิดปัญหานี้

ปัญหานี้เกิดขึ้น 3 ครั้งติดต่อกันตอนนี้ สิ่งที่ฉันได้พบก็คือว่าเมื่อมันไม่เกิดขึ้นมันเกิดขึ้นหนึ่งครั้งหรือมากกว่าในแถวในตารางที่ระบุนั้นไม่ได้อีกครั้ง ดูเหมือนว่าจะเกิดขึ้นสำหรับทุกตารางก่อนที่จะหยุดอย่างสมบูรณ์สำหรับวันที่เกิดขึ้นเป็นเวลาอย่างน้อยหนึ่งนาทีหรือดังนั้นต่อตารางเมื่อมันเกิดขึ้นและจะเกิดขึ้นเป็นระยะ ๆ เท่านั้น (ไม่ตารางทั้งหมดทันที)

ความจริงที่ว่าข้อผิดพลาดนี้เป็นระยะ ๆ (เกิดขึ้นเพียง 3 ครั้งหรือมากกว่านั้นใน 2 สัปดาห์ - ไม่มีการโหลดอื่น ๆ บนฐานข้อมูลเพียงฉันทดสอบใบสมัครของฉัน) เป็นสิ่งที่ทำให้ฉันระวังปัญหาระดับต่ำ


Django ระบุว่าคีย์หลักถูกสร้างขึ้นโดย DBMS ยกเว้นที่ระบุไว้ - ตอนนี้ฉันไม่รู้ว่า @orokusaky กำลังทำอะไรในรหัสไพ ธ อนของเขา แต่ฉันลงเอยที่หน้านี้เพราะฉันค่อนข้างมั่นใจว่าฉันไม่มีรหัส พยายามใช้คีย์หลักที่เฉพาะเจาะจงและฉันไม่เคยเห็น DBMS พยายามที่จะใช้ผิด
mccc

คำตอบ:


34

PostgreSQL จะไม่พยายามแทรกค่าที่ซ้ำกันในตัวของมันเองนั่นคือคุณ (แอปพลิเคชันของคุณ, ORM รวมอยู่ด้วย) ใครทำ

มันอาจเป็นลำดับที่ป้อนค่าให้กับชุด PK ไปยังตำแหน่งที่ไม่ถูกต้องและตารางที่มีค่าเท่ากับnextval()- หรือเพียงแค่ว่าแอปพลิเคชันของคุณทำสิ่งผิด อันแรกง่ายต่อการแก้ไข:

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

อันที่สองหมายถึงการดีบัก

Django (หรือกรอบงานยอดนิยมอื่น ๆ ) ไม่รีเซ็ตลำดับของตนเอง - มิฉะนั้นเราจะมีคำถามที่คล้ายกันทุกวัน


มันน่าสังเกตหรือไม่ (ตามคำตอบของ @ andi ในที่นี้) เกี่ยวกับระดับการแยกที่แตกต่างกัน? ตัวอย่างเช่นหากแบบสอบถามที่สองเข้ามาก่อนที่จะเสร็จสิ้นเป็นครั้งแรกเป็นไปได้หรือไม่ที่จะเกิดสถานการณ์ที่ฉันไม่ได้ใช้ธุรกรรมให้แทรกระเบียนที่เป็นผลลัพธ์ในการรับmax(id)ก่อนที่แบบสอบถามแรกจะเสร็จสิ้น ผลลัพธ์เดียวกัน
orokusaki

5

คุณมีแนวโน้มที่จะแทรกแถวในตารางที่ไม่มีการปรับปรุงค่าลำดับคอลัมน์แบบอนุกรม

พิจารณาคอลัมน์ต่อไปนี้ในตารางของคุณซึ่งเป็นคีย์หลักที่กำหนดโดย Django ORM สำหรับ postgres

id serial NOT NULL

ค่าเริ่มต้นของผู้ถูกตั้งค่าเป็น

nextval('table_name_id_seq'::regclass)

ลำดับจะได้รับการประเมินก็ต่อเมื่อฟิลด์ id ถูกตั้งค่าเป็นช่องว่าง แต่นั่นเป็นปัญหาหากมีรายการอยู่ในตารางอยู่แล้ว

คำถามคือทำไมรายการก่อนหน้านั้นไม่ทริกเกอร์การอัปเดตลำดับ นั่นเป็นเพราะค่า ID ถูกระบุไว้อย่างชัดเจนสำหรับรายการก่อนหน้าทั้งหมด

ในกรณีของฉันรายการเริ่มต้นเหล่านั้นถูกโหลดจากการแข่งขันผ่านการโยกย้าย

ปัญหานี้ยังสามารถรับยุ่งยากผ่านรายการที่กำหนดเองด้วยค่า PK แบบสุ่ม

พูดเพื่อ มี 10 รายการในตารางของคุณ คุณป้อนข้อมูลอย่างชัดเจนด้วย PK = 15 ส่วนแทรกสี่รหัสผ่านถัดไปจะทำงานได้อย่างสมบูรณ์แบบ แต่ส่วนที่ 5 จะเพิ่มข้อยกเว้น

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

ขอบคุณสำหรับโพสต์นี้ ฉันได้แก้จุดบกพร่องกรณีเช่นนี้มาเป็นเวลานาน มันเกิดขึ้นน้อยมาก มันกลับกลายเป็นว่าฟังก์ชั่นการดูแลระบบ "แบบแมนนวล" ที่เฉพาะเจาะจงสามารถแทรกรหัสของตนเองได้โดยปล่อยให้ตัวนับข้อมูลประจำตัวมีค่าเก่า นี่คืออันตรายที่แท้จริงด้วย "สร้างโดยค่าเริ่มต้นเป็นตัวตน" ฉันจะคิดสองครั้งก่อนที่จะใช้ "ตามค่าเริ่มต้น" แทน "เสมอ" ในครั้งต่อไปที่ฉันกำหนดคอลัมน์ข้อมูลประจำตัว
Michael

4

ฉันลงเอยด้วยข้อผิดพลาดเดียวกันซึ่งเกิดขึ้นน้อยมากและติดตามยากเพราะฉันมองหาไม่ใช่ที่ฉันควรทำ

ความผิดพลาดคือการทำซ้ำ JS ซึ่งทำ POST ไปยังเซิร์ฟเวอร์สองครั้ง! ดังนั้นบางครั้งมันก็คุ้มค่าที่จะได้ดูไม่เพียง แต่ใน django ของคุณ (หรือกรอบเว็บอื่น ๆ ) และรูปแบบ แต่ยังเกิดอะไรขึ้นกับด้านหน้า


1

ใช่สิ่งที่แปลก ในกรณีของฉันเห็นได้ชัดว่ามีสิ่งผิดปกติเกิดขึ้นขณะโหลดข้อมูลในการย้ายข้อมูล ฉันเพิ่มการโยกย้ายที่ว่างเปล่าและเขียนบรรทัดเพื่อเพิ่มข้อมูลเริ่มต้นจำนวน 6 รายการในกรณีของฉัน

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)

จากนั้นในแผงผู้ดูแลระบบฉันพยายามที่จะเพิ่มรายการใหม่และได้รับ:

ความพยายามครั้งแรก:

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

ความพยายามในภายหลัง:

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.

และในที่สุดก็ถึงวันที่ 7 และในบางครั้งก็ประสบความสำเร็จ

ดังนั้นฉันกำลังพูดว่าอาจมีบางสิ่งที่เกี่ยวข้องกับ bulk_create ขณะที่ฉันโหลด 6 รายการที่นั่น อาจเป็นสิ่งที่คล้ายกันในโครงการ Django ของคุณที่ทำให้เกิด

Django 1.9 PostgreSQL 9.3.14

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.