สภาวะการแข่งขัน (race conditions) ในแอป CRUD อาจทำให้คำสั่งซื้อซ้ำ ยอดผิดพลาด และสถานะสลับไปมา เรียนรู้จุดชนทั่วไปและวิธีแก้ด้วยข้อจำกัดฐานข้อมูล ล็อกธุรกรรม และกลไกป้องกันฝั่ง UI

สภาวะการชน (race condition) เกิดเมื่อคำขอสองคำขอ (หรือมากกว่า) อัพเดตข้อมูลชิ้นเดียวกันเกือบพร้อมกัน และผลลัพธ์สุดท้ายขึ้นกับจังหวะเวลา คำขอแต่ละอันดูถูกต้องเมื่อแยกกัน แต่เมื่อมารวมกันแล้วกลับให้ผลที่ผิดพลาด
ตัวอย่างง่ายๆ: สองคนคลิกบันทึกบนเร็กคอร์ดลูกค้าชุดเดียวกันภายในเวลาไม่กี่วินาที คนหนึ่งอัพเดตอีเมล อีกคนอัพเดตเบอร์โทร ถ้าคำขอทั้งสองส่งเร็กคอร์ดเต็ม การเขียนครั้งที่สองอาจเขียนทับการเปลี่ยนแปลงครั้งแรก และการเปลี่ยนแปลงหนึ่งอันหายไปโดยไม่มีข้อผิดพลาด
คุณจะเห็นปัญหานี้บ่อยในแอปที่เร็ว เพราะผู้ใช้สามารถกระตุ้นการกระทำได้หลายครั้งต่อนาที และจะพุ่งขึ้นในช่วงเวลาคับคั่ง: งานลดราคาชั่วคราว รายงานสิ้นเดือน แคมเปญอีเมลขนาดใหญ่ หรือเมื่อคำขอจำนวนมากชนกันบนแถวเดียวกัน
ผู้ใช้ไม่ค่อยรายงานว่า “เกิด race condition” พวกเขารายงานอาการ: คำสั่งซื้อหรือคอมเมนต์ซ้ำ อัพเดตหาย ("ฉันบันทึกแล้ว แต่มันกลับไปเหมือนเดิม") ยอดผิดปกติ (สต็อกติดลบ เคาน์เตอร์ถอยหลัง) หรือสถานะที่พลิกไปมา (อนุมัติแล้วกลับเป็นรอดำเนินการ)
การรีทรายทำให้แย่ลง ผู้คนดับเบิลคลิก รีเฟรชหากตอบสนองช้า ส่งฟอร์มจากแท็บสองแท็บ หรือเครือข่ายไม่เสถียรที่ทำให้เบราว์เซอร์และแอปลอกคำขอใหม่ ถ้าเซิร์ฟเวอร์ถือว่าทุกคำขอเป็นการเขียนใหม่ทั้งหมด คุณอาจได้การสร้างสองครั้ง เก็บเงินสองครั้ง หรือการเปลี่ยนสถานะที่ควรเกิดครั้งเดียวกลับเกิดสองครั้ง
แอป CRUD ส่วนใหญ่ดูเรียบง่าย: อ่านแถว แก้ฟิลด์ แล้วบันทึก ปัญหาคือแอปของคุณไม่ได้ควบคุมจังหวะเวลา ฐานข้อมูล เครือข่าย การรีทราย งานพื้นหลัง และพฤติกรรมผู้ใช้ล้วนทับซ้อนกัน
ทริกเกอร์ทั่วไปคือคนสองคนแก้ไขเร็กคอร์ดเดียวกัน ทั้งคู่โหลดค่าปัจจุบันเดียวกันและทำการเปลี่ยนแปลงที่ถูกต้อง และการบันทึกครั้งหลังสุดจะเขียนทับการเปลี่ยนแปลงครั้งแรก ไม่มีใครผิด แต่การอัพเดตหนึ่งอันหายไป
มันยังเกิดได้กับคนเดียวด้วย การดับเบิลคลิกปุ่มบันทึก แตะซ้ำเพราะการเชื่อมต่อช้า หรือการกดส่งอีกครั้งถ้าหน้าช้า ถ้า endpoint ไม่เป็น idempotent คุณอาจได้เร็กคอร์ดซ้ำ ชาร์จซ้ำ หรือเลื่อนสถานะไปสองขั้น
การใช้งานสมัยใหม่เพิ่มการทับซ้อนมากขึ้น แท็บหรืออุปกรณ์หลายชิ้นที่ล็อกอินบัญชีเดียวกันอาจยิงอัพเดตขัดแย้งกัน งานพื้นหลัง (อีเมล การเรียกเก็บเงิน ซิงค์ ทำความสะอาด) สามารถแตะแถวเดียวกับคำขอเว็บ การรีทรายแบบอัตโนมัติที่ฝั่งไคลเอนต์ โหลดบาลานเซอร์ หรือรันเนอร์งานสามารถส่งคำขอซ้ำที่สำเร็จแล้ว
ถ้าคุณปล่อยฟีเจอร์เร็ว เร็กคอร์ดเดียวมักถูกอัพเดตจากหลายจุดมากกว่าที่ใครจะจำได้ ถ้าคุณใช้ตัวสร้างแบบแชทอย่าง Koder.ai แอปจะเติบโตเร็วขึ้นอีก ดังนั้นควรถือว่าความขนานเป็นพฤติกรรมปกติ ไม่ใช่กรณีขอบ
race condition ไม่ค่อยปรากฏในการสาธิต "สร้างเร็กคอร์ด" แต่จะปรากฏเมื่อคำขอสองคำขอแตะชิ้นความจริงเดียวกันเกือบพร้อมกัน การรู้จุดร้อนช่วยออกแบบการเขียนที่ปลอดภัยตั้งแต่ต้น
อะไรที่รู้สึกว่า "เพิ่ม 1" อาจพังเมื่อโหลดสูง: ไลก์ จำนวนวิว ยอดรวม หมายเลขใบแจ้งหนี้ หมายเลขบัตร ตั๋ว รูปแบบเสี่ยงคืออ่านค่า เพิ่ม แล้วเขียนกลับ คำขอสองอันอาจอ่านค่าเริ่มต้นเดียวกันแล้วเขียนทับกัน
เวิร์กโฟลว์เช่น Draft -> Submitted -> Approved -> Paid ดูเรียบง่าย แต่การชนกันเกิดบ่อย ปัญหาเริ่มเมื่อสองการกระทำเป็นไปได้พร้อมกัน (อนุมัติและแก้ไข ยกเลิกและชำระเงิน) ถ้าไม่มีการป้องกัน คุณอาจได้เร็กคอร์ดที่ข้ามขั้น พลิกกลับ หรือแสดงสถานะต่างกันในตารางต่างกัน
ปฏิบัติต่อการเปลี่ยนสถานะเหมือนสัญญา: อนุญาตเฉพาะก้าวที่ถูกต้องถัดไป และปฏิเสธอย่างอื่น
ที่นั่งที่เหลือ จำนวนสินค้า ช่องนัดหมาย และฟิลด์ "ความจุที่เหลือ" สร้างปัญหา oversell แบบคลาสสิก ผู้ซื้อสองคนเช็คเอาต์พร้อมกัน เห็นว่ายังมีสินค้า ทั้งคู่สำเร็จ ถ้าฐานข้อมูลไม่ใช่ผู้ตัดสินสุดท้าย ก็จะขายเกินจำนวนได้ในที่สุด
กฎบางอย่างเป็นข้อบังคับ: อีเมลหนึ่งบัญชี หนึ่งการสมัครใช้งานที่ใช้งานต่อผู้ใช้ หนึ่งตะกร้ากำลังเปิดต่อผู้ใช้ มักล้มเหลวเมื่อคุณเช็คก่อน ("มีอยู่หรือไม่?") แล้วค่อยแทรก ภายใต้ความขนานคำขอทั้งสองอาจผ่านการตรวจได้
ถ้าคุณสร้างฟลอว์ CRUD อย่างรวดเร็ว (เช่นโดยคุยสร้างแอปบน Koder.ai) จดจุดร้อนเหล่านี้ตั้งแต่ต้นและหนุนด้วยข้อจำกัดและการเขียนที่ปลอดภัย ไม่ใช่แค่การตรวจฝั่ง UI
หลาย race condition เริ่มจากสิ่งน่าเบื่อ: การกระทำเดียวกันถูกส่งสองครั้ง ผู้ใช้ดับเบิลคลิก เครือข่ายช้าเลยคลิกอีกครั้ง โทรศัพท์ลงทะเบียนสองครั้ง บางครั้งไม่ตั้งใจก็เกิดขึ้น: หน้ารีเฟรชหลัง POST และเบราว์เซอร์เสนอให้ส่งซ้ำ
เมื่อนั้น backend ของคุณอาจรันการสร้างหรืออัพเดตสองครั้งพร้อมกัน ถ้าทั้งสองสำเร็จ คุณจะได้เร็กคอร์ดซ้ำ ยอดผิด หรือการเปลี่ยนสถานะที่เกิดสองครั้ง มันดูสุ่มเพราะขึ้นกับจังหวะเวลา
แนวทางที่ปลอดภัยที่สุดคือการป้องกันทั้งชั้น ฟิก UI แต่สมมติว่า UI อาจล้มเหลว
การเปลี่ยนแปลงเชิงปฏิบัติที่ใช้ได้กับ flow การเขียนส่วนใหญ่:
ตัวอย่าง: ผู้ใช้กด "ชำระเงิน" สองครั้งบนมือถือ UI ควรบล็อกการแตะที่สอง เซิร์ฟเวอร์ควรปฏิเสธคำขอที่สองเมื่อเห็น idempotency key เดิม และคืนผลลัพธ์ความสำเร็จเดิมแทนการเก็บเงินซ้ำ
ฟิลด์สถานะดูเรียบง่ายจนกระทั่งสองสิ่งพยายามเปลี่ยนมันพร้อมกัน ผู้ใช้คลิกอนุมัติในขณะที่งานอัตโนมัติทำเครื่องหมายว่า Expired ทั้งสองอัพเดตอาจสำเร็จ แต่สถานะสุดท้ายขึ้นกับจังหวะเวลา ไม่ใช่กฎของคุณ
ปฏิบัติต่อสถานะเหมือนเครื่องสถานะขนาดเล็ก เก็บตารางสั้นๆ ของการย้ายที่อนุญาต (เช่น: Draft -> Submitted -> Approved และ Submitted -> Rejected) แล้วทุกการเขียนให้ตรวจสอบ: "การย้ายนี้อนุญาตจากสถานะปัจจุบันหรือไม่?" ถ้าไม่ ให้ปฏิเสธแทนที่จะเงียบๆ เขียนทับ
การล็อกแบบ optimistic ช่วยจับการอัพเดตที่ล้าสมัยโดยไม่บล็อกผู้ใช้อื่น เพิ่มหมายเลขเวอร์ชัน (หรือ updated_at) แล้วบังคับให้ตรงเมื่อบันทึก ถ้ามีคนอื่นเปลี่ยนแถวหลังจากคุณโหลดมา การอัพเดตของคุณจะไม่มีแถวใดถูกกระทบและคุณสามารถแสดงข้อความชัดเจนเช่น "รายการนี้มีการเปลี่ยนแปลง กรุณารีเฟรชแล้วลองใหม่"
แพตเทิร์นเรียบง่ายสำหรับการอัพเดตสถานะคือ:
นอกจากนี้ ให้เก็บการเปลี่ยนสถานะไว้ที่เดียว ถ้าการอัพเดตกระจัดกระจายทั้งหน้าจอ งานพื้นหลัง และ webhook คุณจะพลาดกฎ วางไว้หลังฟังก์ชันหรือ endpoint เดียวที่บังคับใช้การตรวจเหมือนกันทุกครั้ง
บั๊กเคาน์เตอร์ที่พบบ่อยสุดดูไม่เป็นพิษเป็นภัย: แอปอ่านค่า เพิ่ม 1 แล้วเขียนกลับ ภายใต้โหลด คำขอสองอันอาจอ่านเลขเดียวกันและทั้งคู่เขียนค่าเดียวกัน ดังนั้นหนึ่งการเพิ่มหายไป มันง่ายพลาดเพราะ "ส่วนใหญ่ใช้ได้" ในการทดสอบ
ถ้าค่าถูกเพิ่มหรือลด ให้ฐานข้อมูลทำในคำสั่งเดียว แล้วฐานข้อมูลจะประยุกต์การเปลี่ยนแปลงอย่างปลอดภัยแม้มีคำขอหลายครั้งพร้อมกัน
UPDATE posts
SET like_count = like_count + 1
WHERE id = $1;
แนวคิดเดียวกันใช้กับสต็อก จำนวนวิว ตัวนับรีทราย และสิ่งที่สามารถเขียนเป็น "ใหม่ = เก่า + ส่วนต่าง" ได้
ยอดรวมมักพังเมื่อคุณเก็บตัวเลขที่ได้จากการคำนวณ (order_total, account_balance, project_hours) แล้วอัพเดตจากหลายที่ ถ้าคุณสามารถคำนวณยอดจากแถวแหล่งข้อมูล (รายการสินค้า ระเบียนบัญชี ชั่วโมงงาน) คุณจะหลีกเลี่ยงบั๊กการเลื่อนทั้งประเภทได้
เมื่อจำเป็นต้องเก็บยอดเพื่อความเร็ว ให้ถือมันเป็นการเขียนที่สำคัญ เก็บการอัพเดตของแถวแหล่งข้อมูลและยอดรวมที่เก็บไว้ในธุรกรรมเดียวกัน ตรวจสอบให้มีผู้เขียนเพียงคนเดียวสามารถอัพเดตยอดเดียวกันได้ในเวลาเดียวกัน (การล็อก การอัพเดตที่มีเงื่อนไข หรือเส้นทางเจ้าของเดียว) เพิ่มข้อจำกัดที่ป้องกันค่าที่เป็นไปไม่ได้ (เช่น สต็อก < 0) แล้วทำการตรวจสอบความถูกต้องเป็นระยะโดยแบ็กกราวด์ที่คำนวณใหม่และส่งสัญญาณเมื่อไม่ตรงกัน
ตัวอย่างชัดเจน: สองคนเพิ่มสินค้าลงตะกร้าเดียวกันพร้อมกัน ถ้าคำขอแต่ละอันอ่าน cart_total เพิ่มราคาสินค้า แล้วเขียนกลับ การเพิ่มหนึ่งอันอาจหายไป หากคุณอัพเดตรายการตะกร้าและ cart total พร้อมกันในธุรกรรมหนึ่ง ยอดจะถูกต้องแม้มีการคลิกพร้อมกันหนาแน่น
ถ้าต้องการลด race condition ให้เริ่มที่ฐานข้อมูล โค้ดแอปอาจรีทราย หมดเวลา หรือรันสองครั้ง ข้อจำกัดในฐานข้อมูลคือประตูสุดท้ายที่ถูกต้องแม้คำขอสองคำขอกระทบพร้อมกัน
ข้อจำกัดความเป็นเอกลักษณ์หยุดการซ้ำที่ "ไม่ควรเกิด" แต่เกิดได้: ที่อยู่อีเมล หมายเลขคำสั่ง หมายเลขใบแจ้งหนี้ หรือกฎ "มีการสมัครใช้งานที่ใช้งานได้หนึ่งรายการต่อผู้ใช้" เมื่อสองการลงทะเบียนมาพร้อมกัน ฐานข้อมูลจะยอมรับแถวหนึ่งและปฏิเสธอีกอัน
foreign keys ป้องกันการอ้างอิงที่พัง ถ้าไม่มี พ่อแม่ถูกลบขณะที่อีกคำขอสร้างลูกที่ชี้ไปยังสิ่งที่ไม่อยู่ จะทิ้งแถวเด็กที่ไม่มีที่มาให้แก้ไขยาก
check constraints คงค่าภายในช่วงปลอดภัยและบังคับกฎสถานะง่ายๆ เช่น quantity >= 0 rating ระหว่าง 1 ถึง 5 หรือสถานะจำกัดให้อยู่ในชุดที่อนุญาต
มองว่าการผิดพลาดจากข้อจำกัดเป็นผลลัพธ์ที่คาดได้ ไม่ใช่ "ข้อผิดพลาดเซิร์ฟเวอร์" จับการละเมิด unique foreign key และ check แล้วคืนข้อความชัดเจนเช่น "อีเมลนี้ถูกใช้งานแล้ว" และบันทึกรายละเอียดเพื่อตรวจโดยไม่รั่วไหลข้อมูลภายใน
ตัวอย่าง: สองคนคลิก "สร้างคำสั่งซื้อ" สองครั้งในช่วงหน่วง หากมี unique constraint บน (user_id, cart_id) คุณจะไม่ได้คำสั่งซื้อสองรายการ แต่ได้คำสั่งซื้อหนึ่งรายการและการปฏิเสธที่อธิบายได้สำหรับอีกคำขอ
การเขียนบางอย่างไม่ใช่คำสั่งเดียว คุณอ่านแถว ตรวจสอบกฎ อัพเดตสถานะ และอาจแทรกบันทึกตรวจสอบ ถ้าคำขอสองอันทำแบบนั้นพร้อมกัน ทั้งคู่อาจผ่านการตรวจและทั้งคู่เขียน นี่คือรูปแบบความล้มเหลวแบบคลาสสิก
ห่อการเขียนหลายขั้นตอนในธุรกรรมเดียวเพื่อให้ทุกขั้นตอนสำเร็จพร้อมกันหรือไม่เลย ยิ่งสำคัญ ธุรกรรมยังเป็นที่ควบคุมว่าใครเปลี่ยนข้อมูลเดียวกันได้พร้อมกัน
เมื่อมีผู้ทำหน้าที่เดียวเท่านั้นที่สามารถแก้ไขเร็กคอร์ดได้ในครั้งเดียว ให้ใช้การล็อกแถวระดับแถว เช่น ล็อกแถวคำสั่ง ซื้อ ยืนยันว่ายังคงเป็นสถานะ "รอดำเนินการ" แล้วเปลี่ยนเป็น "อนุมัติ" และเขียนบันทึกตรวจสอบ คำขอที่สองจะรอ แล้วตรวจสถานะใหม่และหยุด
เลือกตามความถี่ที่เกิดการชน:
ให้เวลาในการถือล็อกสั้น ทำงานน้อยที่สุดขณะถือมัน: ห้ามเรียก API ภายนอก โต้ตอบไฟล์ช้า หรือลูปใหญ่ หากคุณสร้างฟลอว์ในเครื่องมือเช่น Koder.ai ให้เก็บธุรกรรมแค่ขั้นตอนฐานข้อมูล แล้วทำส่วนที่เหลือหลัง commit
เลือก flow หนึ่งที่การชนอาจทำให้เสียเงินหรือความเชื่อถือได้ ตัวอย่างทั่วไปคือ: สร้างคำสั่งซื้อ สำรองสต็อก แล้วตั้งสถานะคำสั่งซื้อเป็นยืนยัน
เขียนขั้นตอนที่โค้ดคุณทำตอนนี้ทีละข้อ ระบุชัดเจนว่าอ่านอะไร เขียนอะไร และ "สำเร็จ" หมายถึงอะไร การชนซ่อนตัวในช่องว่างระหว่างการอ่านและการเขียนภายหลัง
เส้นทางเสริมความแข็งที่ใช้ได้ในสแต็กส่วนใหญ่:
เพิ่มเทสต์หนึ่งตัวที่พิสูจน์การแก้ไข รันคำขอสองคำขอพร้อมกันกับสินค้าและปริมาณเดียวกัน ยืนยันว่าเพียงคำสั่งซื้อเดียวเท่านั้นที่ยืนยัน และอีกคำขอล้มเหลวอย่างควบคุม (ไม่มีสต็อกติดลบ ไม่มีแถวการสำรองซ้ำ)
ถ้าคุณสร้างแอปอย่างรวดเร็ว (รวมถึงด้วยแพลตฟอร์มอย่าง Koder.ai) เช็คลิสต์นี้ยังคุ้มค่ากับการทำในเส้นทางการเขียนที่สำคัญไม่กี่เส้น
สาเหตุใหญ่หนึ่งคือเชื่อใจ UI เกินไป ปุ่มที่ปิดและการตรวจฝั่งไคลเอนต์ช่วยได้ แต่ผู้ใช้ดับเบิลคลิก รีเฟรช เปิดสองแท็บ หรือเล่นคำขอกลับได้ หากเซิร์ฟเวอร์ไม่เป็น idempotent การซ้ำจะเล็ดรอด
บั๊กเงียบอีกอย่าง: คุณจับข้อผิดพลาดฐานข้อมูล (เช่น unique constraint violation) แต่ยังคงดำเนินเวิร์กโฟลว์ต่อ นั่นมักกลายเป็น "สร้างล้มเหลว แต่เรายังส่งอีเมล" หรือ "ชำระเงินล้มเหลว แต่เรายังทำเครื่องหมายคำสั่งเป็นชำระแล้ว" เมื่อผลด้านข้างเกิดขึ้น ยากจะย้อนกลับ
ธุรกรรมยาวๆ ก็เป็นกับดัก ถ้าคุณเปิดธุรกรรมขณะเรียกอีเมล การชำระเงิน หรือ API ภายนอก คุณจะถือล็อกนานเกินจำเป็น เพิ่มการรอและเวลาหมด และโอกาสที่คำขอจะบล็อกกัน
การผสมงานพื้นหลังกับการกระทำผู้ใช้โดยไม่มีแหล่งความจริงเดียวสร้างสภาวะ split-brain งานรีทรายแล้วอัพเดตแถวในขณะที่ผู้ใช้กำลังแก้ไข และทั้งสองคิดว่าตัวเองเป็นคนแก้ล่าสุด
"การแก้" บางอย่างที่ไม่ใช่การแก้จริง:
ถ้าคุณสร้างด้วยเครื่องมือคุยเป็นแอปอย่าง Koder.ai กฎเดียวกันใช้ได้: ขอให้มีข้อจำกัดฝั่งเซิร์ฟเวอร์และขอบเขตธุรกรรมที่ชัดเจน ไม่ใช่แค่การป้องกัน UI ที่ดูดี
race condition มักปรากฏเฉพาะภายใต้ทราฟฟิกจริง การผ่านตรวจก่อนปล่อยสามารถจับจุดชนกันทั่วไปโดยไม่ต้องรื้อระบบทั้งแอป
เริ่มจากฐานข้อมูล ถ้าบางอย่างต้องเป็นเอกลักษณ์ (อีเมล หมายเลขใบแจ้งหนี้ หนึ่งกฎที่ต้องเป็นเอกลักษณ์) ให้ทำเป็น unique constraint แทนการเช็คในแอป แล้วตรวจว่ารหัสของคุณคาดหวังว่าข้อจำกัดจะล้มบางครั้งและคืนการตอบกลับที่ชัดเจนและปลอดภัย
ถัดมา ดูสถานะ การเปลี่ยนสถานะใดๆ ควรได้รับการตรวจสอบกับชุดการเปลี่ยนผ่านที่ชัดเจน หากสองคำขอพยายามย้ายเร็กคอร์ดเดียวกัน คำขอที่สองควรถูกปฏิเสธหรือกลายเป็น no-op ไม่ใช่สร้างสถานะอยู่กลางทาง
เช็กลิสต์ปฏิบัติ:
ถ้าคุณสร้าง flow บน Koder.ai ให้ถือสิ่งเหล่านี้เป็นเกณฑ์ยอมรับ: แอปที่สร้างควรล้มอย่างปลอดภัยเมื่อมีการรีพีทและความขนาน ไม่ใช่แค่ผ่านเส้นทางปกติ
พนักงานสองคนเปิดคำขอซื้อเดียวกัน ทั้งคู่คลิกอนุมัติภายในไม่กี่วินาที คำขอทั้งสองมาถึงเซิร์ฟเวอร์
สิ่งที่อาจผิดเกิดขึ้นได้หลายอย่าง: คำขอถูก "อนุมัติ" สองครั้ง การแจ้งเตือนส่งสองครั้ง และยอดที่ผูกกับการอนุมัติ (งบประมาณที่ใช้ จำนวนการอนุมัติต่อวัน) กระโดดขึ้นสอง ทั้งสองอัพเดตถูกต้องเมื่อแยกกัน แต่ชนกันเมื่อรวมกัน
นี่คือแผนแก้ที่ใช้ได้ดีกับฐานข้อมูลสไตล์ PostgreSQL
เพิ่มกฎที่รับประกันว่ามีเร็กคอร์ดการอนุมัติเดียวเท่านั้นสำหรับคำขอ เช่น เก็บการอนุมัติไว้ในตารางแยกและบังคับ unique constraint บน request_id ตอนนี้การแทรกครั้งที่สองจะล้มแม้โค้ดแอปมีบั๊ก
เมื่ออนุมัติ ให้ทำการเปลี่ยนทั้งหมวดในธุรกรรมเดียว:
ถ้าพนักงานคนที่สองมาตอนหลัง เขาจะเห็นว่าจำนวนแถวที่อัพเดตเป็น 0 หรือเกิด unique-constraint error ไม่ว่าแบบไหนก็มีเพียงการเปลี่ยนเดียวที่ชนะ
หลังแก้ ไมล์แรกจะเห็นว่า Approved และได้การยืนยันตามปกติ คนที่สองจะเห็นข้อความเป็นมิตรเช่น: "คำขอนี้ได้รับการอนุมัติแล้วโดยคนอื่น กรุณารีเฟรชเพื่อดูสถานะล่าสุด" ไม่มีการหมุน ไม่แจ้งซ้ำ ไม่มีความล้มเหลวเงียบ
ถ้าคุณสร้าง CRUD flow ด้วยแพลตฟอร์มอย่าง Koder.ai (backend เป็น Go กับ PostgreSQL) คุณสามารถฝังเช็คนี้ไว้ใน action อนุมัติครั้งเดียวและนำแพตเทิร์นนี้ไปใช้กับการกระทำ "ผู้ชนะหนึ่งเดียว" อื่นๆ
race condition แก้ได้ง่ายเมื่อคุณทำให้เป็นกิจวัตรที่ทำซ้ำได้ ไม่ใช่การล่าบั๊กครั้งเดียว ให้โฟกัสที่เส้นทางการเขียนไม่กี่เส้นที่สำคัญที่สุด และทำให้พวกมันถูกต้องก่อนจะขัดเกลาอย่างอื่น
เริ่มจากตั้งชื่อจุดชนสูงสุดของคุณ ในหลายแอป CRUD มันคือสามสิ่งเดียวกัน: เคาน์เตอร์ (ไลก์ สต็อก ยอดเงิน) การเปลี่ยนสถานะ (Draft -> Submitted -> Approved) และการส่งซ้ำ (ดับเบิลคลิก รีทราย เครือข่ายช้า)
รูทีนที่ทนทานได้:
ถ้าคุณสร้างบน Koder.ai, Planning Mode เป็นที่เหมาะสำหรับแมปแต่ละ flow เป็นขั้นตอนและกฎก่อนสร้างโค้ดจริง Snapshots และ rollback ก็มีประโยชน์เมื่อปล่อยข้อจำกัดหรือพฤติกรรมล็อกใหม่และต้องการทางกลับที่รวดเร็วเมื่อเจอกรณีขอบ
เมื่อเวลาผ่านไป นี่จะกลายเป็นนิสัย: ทุกฟีเจอร์การเขียนใหม่มี constraint ธุรกรรม และเทสต์ความขนาน นั่นแหละคือวิธีที่ race condition ในแอป CRUD หยุดเป็นเรื่องเซอร์ไพรส์