ล็อคใน Postgres สำหรับชุด UPDATE / INSERT


11

ฉันมีสองตาราง หนึ่งคือตารางบันทึก; อีกอันประกอบด้วยรหัสคูปองที่สามารถใช้ได้ครั้งเดียวเท่านั้น

ผู้ใช้ต้องสามารถแลกคูปองซึ่งจะแทรกแถวลงในตารางบันทึกและทำเครื่องหมายคูปองที่ใช้ (โดยอัปเดตusedคอลัมน์เป็นtrue)

โดยธรรมชาติมีปัญหาสภาพการแข่งขัน / ความปลอดภัยที่เห็นได้ชัดที่นี่

ฉันเคยทำสิ่งที่คล้ายกันในอดีตในโลกของ mySQL ในโลกนั้นฉันจะล็อกทั้งสองตารางทั่วโลกทำตรรกะให้ปลอดภัยด้วยความรู้ที่ว่าสิ่งนี้จะเกิดขึ้นได้ครั้งละครั้งแล้วปลดล็อกตารางเมื่อฉันทำเสร็จแล้ว

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

คำตอบ:


15

ฉันเคยได้ยินปัญหาการทำงานพร้อมกันเช่นนั้นใน MySQL มาก่อน ไม่เช่นนั้นใน Postgres

การล็อคระดับแถวในตัวในREAD COMMITTEDระดับแยกธุรกรรมเริ่มต้นก็เพียงพอแล้ว

ฉันขอแนะนำคำสั่งเดียวที่มีการแก้ไขข้อมูล CTE (สิ่งที่ MySQL ยังไม่มี) เพราะสะดวกในการส่งผ่านค่าจากตารางหนึ่งไปยังอีกโดยตรง (ถ้าคุณต้องการ) หากคุณไม่ต้องการอะไรจากcouponตารางคุณสามารถใช้ธุรกรรมที่มีการแยกUPDATEและINSERTงบได้เช่นกัน

WITH upd AS (
   UPDATE coupon
   SET    used = true
   WHERE  coupon_id = 123
   AND    NOT used
   RETURNING coupon_id, other_column
   )
INSERT INTO log (coupon_id, other_column)
SELECT coupon_id, other_column FROM upd;

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

เป็นไปตามที่อาจUPDATEทำได้สำเร็จเพียงธุรกรรมเดียวเท่านั้นไม่ว่าจะเกิดอะไรขึ้น การUPDATEรับล็อคระดับแถวในแต่ละแถวเป้าหมายก่อนอัปเดต หากการทำธุรกรรมพร้อมกันพยายามที่จะUPDATEแถวเดียวกันมันจะเห็นล็อคในแถวและรอจนกว่าการปิดกั้นการทำธุรกรรมเสร็จสิ้น ( ROLLBACKหรือCOMMIT) จากนั้นเป็นคนแรกในคิวล็อค:

  • หากมุ่งมั่นให้ตรวจสอบสภาพอีกครั้ง หากยังคงNOT usedล็อคแถวและดำเนินการต่อ อื่นUPDATEขณะนี้พบว่าไม่มีแถวที่มีคุณสมบัติและไม่มีอะไรกลับไม่มีแถวดังนั้นINSERTยังไม่ทำอะไรเลย

  • หากย้อนกลับให้ล็อคแถวและดำเนินการต่อ

มีศักยภาพในการแย่งไม่มี

ไม่มีความเป็นไปได้สำหรับการหยุดชะงักหากคุณไม่ได้ใส่การเขียนลงในธุรกรรมเดียวกันหรือล็อกแถวมากกว่าที่จะเขียน

ไม่INSERTต้องห่วง หากมีข้อผิดพลาดcoupon_idอยู่แล้วในlogตาราง (และคุณมีข้อ จำกัด UNIQUE หรือ PK log.coupon_id) ธุรกรรมทั้งหมดจะถูกย้อนกลับหลังจากการละเมิดที่ไม่ซ้ำกัน จะระบุสถานะที่ผิดกฎหมายในฐานข้อมูลของคุณ หากข้อความข้างต้นเป็นวิธีเดียวที่จะเขียนลงในlogตารางนั่นไม่ควรเกิดขึ้น


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