หลีกเลี่ยงการละเมิดที่ไม่ซ้ำกันในการทำธุรกรรมอะตอม


15

เป็นไปได้ในการสร้างธุรกรรมอะตอมมิกใน PostgreSQL?

พิจารณาฉันมีหมวดหมู่ตารางที่มีแถวเหล่านี้:

id|name
--|---------
1 |'tablets'
2 |'phones'

และชื่อคอลัมน์มีข้อ จำกัด ที่ไม่ซ้ำกัน

ถ้าฉันลอง:

BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;

ฉันได้รับ:

ERROR:  duplicate key value violates unique constraint "category_name_key"
DETAIL:  Key (name)=(tablets) already exists.

คำตอบ:


24

นอกจากสิ่งที่@Craig จัดเตรียมไว้ (และแก้ไขบางส่วนแล้ว):

มีผลบังคับใช้ Postgres 9.4 , UNIQUE, PRIMARY KEYและEXCLUDEข้อ จำกัด จะถูกตรวจสอบได้ทันทีหลังจากที่แต่ละแถวNOT DEFERRABLEเมื่อกำหนด ซึ่งแตกต่างจากชนิดอื่น ๆ ของNOT DEFERRABLEข้อ จำกัด (ปัจจุบันเท่านั้นREFERENCES(คีย์ต่างประเทศ)) ซึ่งได้มีการตรวจสอบหลังจากแต่ละคำสั่ง เราทำงานทั้งหมดนี้ภายใต้คำถามที่เกี่ยวข้องนี้ใน SO:

มันเป็นไม่พอสำหรับUNIQUE(หรือPRIMARY KEYหรือEXCLUDE) จำกัด จะเป็นDEFERRABLEที่จะทำให้รหัสนำเสนอของคุณด้วยงบหลายงาน

และคุณไม่สามารถใช้ALTER TABLE ... ALTER CONSTRAINTเพื่อวัตถุประสงค์นี้ ตามเอกสาร:

ALTER CONSTRAINT

แบบฟอร์มนี้จะเปลี่ยนแปลงคุณสมบัติของข้อ จำกัด ที่สร้างขึ้นก่อนหน้านี้ ปัจจุบันข้อ จำกัด ที่สำคัญเท่านั้นต่างประเทศอาจมีการเปลี่ยนแปลง

เหมืองเน้นหนัก ใช้แทน:

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;

ดร็อปและเพิ่มข้อ จำกัด กลับมาในคำสั่งเดียวดังนั้นจึงไม่มีหน้าต่างเวลาสำหรับใครที่จะแอบเข้าไปในแถวที่กระทำผิด สำหรับตารางขนาดใหญ่มันจะดึงดูดการอนุรักษ์ดัชนีที่ไม่ซ้ำกันต้นแบบอย่างใดเพราะมันมีค่าใช้จ่ายสูงในการลบและสร้างใหม่ อนิจจาที่ดูเหมือนจะไม่เป็นไปได้ด้วยเครื่องมือมาตรฐาน (หากคุณมีวิธีแก้ปัญหาโปรดแจ้งให้เราทราบ!):

สำหรับคำสั่งเดียวที่ทำให้การ จำกัด การเลื่อนเวลานั้นเพียงพอแล้ว:

UPDATE category c
SET    name = c_old.name
FROM   category c_old
WHERE  c.id     IN (1,2)
AND    c_old.id IN (1,2)
AND    c.id <> c_old.id;

แบบสอบถามที่มี CTE ก็เป็นคำสั่งเดียว :

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;

อย่างไรก็ตามสำหรับรหัสของคุณที่มีหลายคำสั่งคุณ (เพิ่มเติม) จำเป็นต้องเลื่อนข้อ จำกัด ออกไป - หรือกำหนดเป็นINITIALLY DEFERREDEither โดยทั่วไปจะมีราคาแพงกว่าข้างต้น แต่อาจเป็นไปไม่ได้ที่จะบรรจุทุกอย่างลงในคำสั่งเดียว

BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones'  WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;

ระวังข้อ จำกัด ที่เกี่ยวข้องกับFOREIGN KEYข้อ จำกัด ตามเอกสาร:

คอลัมน์ที่อ้างอิงจะต้องเป็นคอลัมน์ของข้อ จำกัด คีย์ที่ไม่ซ้ำกันหรือคีย์หลักในตารางอ้างอิง

ดังนั้นคุณไม่สามารถมีทั้งสองอย่างในเวลาเดียวกัน


13

ตามที่ฉันเข้าใจแล้วปัญหาของคุณที่นี่คือข้อ จำกัด ถูกตรวจสอบหลังจากแต่ละคำสั่ง แต่คุณต้องการให้มีการตรวจสอบในตอนท้ายของธุรกรรมดังนั้นจึงทำการเปรียบเทียบสถานะก่อนหน้ากับสถานะ after-state

ถ้าเป็นเช่นนั้นนั่นเป็นไปได้ด้วยข้อ จำกัด ที่เลื่อนออกไป

ดูSET CONSTRAINTSและDEFERRABLEข้อ จำกัด CREATE TABLEในเอกสาร

โปรดทราบว่าข้อ จำกัด รอการตัดบัญชีมีค่าใช้จ่าย - ระบบจะต้องเก็บรายการของพวกเขาเพื่อตรวจสอบเวลาที่กระทำดังนั้นจึงไม่ดีสำหรับการทำธุรกรรมที่ทำให้เกิดการเปลี่ยนแปลงครั้งใหญ่ พวกเขายังช้ากว่าในการตรวจสอบ

ดังนั้นฉันคิดว่าคุณอาจต้องการ:

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

โปรดทราบว่ามีข้อ จำกัด ในALTER TABLEการตั้งค่าข้อ จำกัด เป็นDEFERRABLE; คุณอาจต้องแทนDROPและADDจำกัดอีกครั้ง

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