#faq#concurrency#lock#deadlock#troubleshooting#backend#dev
หลายคนบันทึกเอกสารพร้อมกันแล้วช้า/ค้าง/ขึ้น deadlock หรือเลขที่ซ้ำ ระบบกันอย่างไร
ตอบสั้น: การบันทึก/ลบ/ยกเลิกเอกสารใช้ PostgreSQL advisory lock ภายใน tx เดียวกัน 3 ชั้นตามลำดับคงที่: (1) ล็อกเอกสาร ic_trans:<เลขที่>:<flag> (2) ล็อก GL แบบ shared (3) ล็อก shared ของสต็อกทั้งฐาน แล้วล็อกรายสินค้าเรียงรหัส ล็อกพวกนี้ปล่อยเองตอน commit/rollback อาการ "รอนาน" ส่วนมากคือรอ rebuild GL ช่วงวันที่ (ล็อก exclusive) หรือรอเอกสารอื่นที่ถือสินค้าเดียวกัน ไม่ใช่ระบบค้าง
รายละเอียด
ลำดับล็อกใน saveTransTx (trans.go:510):
lockTransDoc(:473):pg_advisory_xact_lock(hashtext("ic_trans:<doc>:<flag>"))— กันบันทึก/ลบเลขเดียวกันซ้อนกัน จากนั้นlockGLSharedทันที แล้วอ่านรายการสินค้า/เลขเช็คที่เอกสารเดิมถืออยู่lockStockItems(service.go:992) รับสินค้าของเอกสารเดิม + ใหม่ รวมเป็นuniqueSortedแล้ว: ล็อก sharedic_inventory:rebuildและล็อกรายสินค้าic_stock:<code>เรียงตามรหัส ทำให้สองเอกสารที่มีสินค้าเดียวกันหลายตัวไม่ล็อกสลับลำดับ (สาเหตุของ deadlock เดิม: บันทึกที่ทำให้ต้นทุนเปลี่ยนไปแก้แถวic_trans_detailของเอกสารอื่นในสินค้าเดียวกัน ดู ทำไมต้นทุนของเอกสารเก่าเปลี่ยนเอง ทั้งที่ไม่ได้แก้เอกสารนั้น)rebuildTransDerivedใช้uniqueSorted(items)อีกรอบ (trans.go:595) เพื่อ rebuild สต็อกในลำดับคงที่
ล็อก exclusive (ตัวที่ทำให้ทุกคนรอ):
RebuildGL(service.go:1297) เปิด tx 60 วินาทีและล็อกgl_journal:rebuildแบบ exclusive ระหว่างลบ-ลงบัญชีใหม่ทั้งช่วงวันที่ ส่วนการบันทึก/อนุมัติ/ยกเลิก/ขายสินทรัพย์ถือ shared จึงทำงานคู่กันได้ แต่ต้องรอ rebuild จบ (ต้องกดประมวลผลลงบัญชี (Rebuild GL) ใหม่เมื่อไร มันลบอะไรทิ้งบ้าง)RebuildStockทั้งฐาน (ไม่ระบุสินค้า) ล็อกic_inventory:rebuildแบบ exclusive (service.go:1014) ทุกเอกสารที่มีสินค้ารอจนจบ (คำนวณยอดสต๊อกใหม่ (Rebuild Stock) ทำอะไรบ้าง ต้องกดเมื่อไหร่)
ล็อกเฉพาะเรื่องอื่น ๆ:
| จุด | ล็อกอะไร | ที่ |
|---|---|---|
| ออกเลขเอกสารสถานะ (ยกเลิก/อนุมัติ) | docno:<table>:<prefix> กันเลขซ้ำ |
doc_cancel.go:433 |
| ใบรับ/จ่ายชำระบิลเดียวกัน | bill:<เลขวางบิล> เรียงด้วย ORDER BY แล้วค่อยตรวจยอดคงเหลือ |
pay_check.go:170 |
| มัดจำที่หลายใบใช้ร่วมกัน | วนล็อกตามลำดับ sorted | trans_deposit.go:147 |
| แก้ต้นทุนแฝง (hidden cost) | ล็อกเลขเอกสารเรียงลำดับ | hidden_cost.go:381 |
| ขายสินทรัพย์ | ล็อก asset-sale ก่อน แล้วล็อก GL shared |
fa_sale.go:406 |
ถ้าเลขที่เอกสารชนกัน: ล็อกเอกสารทำให้คนที่สองรอ แล้ว rejectDuplicate ตอบ "เลขที่เอกสารซ้ำ" (trans.go:1256) ไม่ใช่เขียนทับ (เลขที่เอกสารรันอย่างไร ตั้งรูปแบบเลขที่ที่ไหน)
ข้อควรระวัง
- ค้นโค้ดแล้วไม่พบการแปลง SQLSTATE
40P01เป็นข้อความไทย ถ้ามี deadlock หลุดจริง ผู้ใช้จะเห็น error ภายในดิบ ให้กดบันทึกใหม่ได้เพราะ rollback ทั้งก้อน (บันทึก/ลบเอกสารแล้วข้อมูลไม่ครบหรือเหลือครึ่ง ๆ กลาง ๆ ได้ไหม (atomic transaction ทำงานอย่างไร)) แต่ให้แจ้งผู้พัฒนาพร้อมเลขเอกสารและสินค้า เพราะแปลว่ามีเส้นทางที่ล็อกผิดลำดับ - ข้อจำกัดที่โค้ดเขียนไว้เอง (คอมเมนต์
service.go:1355-1364): ใช้คีย์ GL คีย์เดียวทั้งฐาน การ rebuild ช่วงยาวทำให้ทุกใบรอสูงสุดราว 60 วินาที ถ้า inbox ของ POS รอคิวหลัง rebuild อาจหมดเวลา 60 วินาทีของตัวเอง ทางแก้ที่โค้ดระบุคือเปลี่ยนเป็นคีย์รายงวด ยังไม่ได้ทำ ดังนั้นอย่า rebuild ช่วงยาวช่วงชั่วโมงขายหนัก - ล็อกเป็นแบบ advisory ใช้ได้เฉพาะโค้ดที่เรียก
lock*เท่านั้น ทางเขียนใหม่ที่แตะic_trans_detail/gl_journalต้องล็อกตามลำดับเดียวกัน (เอกสาร, GL shared, สต็อกเรียงรหัส) ก่อนเขียนแถวแรก - เทสต์ที่กันลำดับล็อก (
service_test.go:58,:85) ใช้ tx จำลองตรวจลำดับคำสั่ง ไม่ได้ทดสอบกับ PostgreSQL จริง ประวัติระบุว่าการรัน repro ซ้ำกับฐานจริงยังไม่ได้ยืนยัน (บทเรียนบั๊กการทำงานพร้อมกันและ advisory lock (Concurrency & Advisory Locks))
ดูเพิ่ม
- บันทึก/ลบเอกสารแล้วข้อมูลไม่ครบหรือเหลือครึ่ง ๆ กลาง ๆ ได้ไหม (atomic transaction ทำงานอย่างไร)
- คำนวณยอดสต๊อกใหม่ (Rebuild Stock) ทำอะไรบ้าง ต้องกดเมื่อไหร่
- ต้องกดประมวลผลลงบัญชี (Rebuild GL) ใหม่เมื่อไร มันลบอะไรทิ้งบ้าง
- เลขที่เอกสารรันอย่างไร ตั้งรูปแบบเลขที่ที่ไหน
- บทเรียนบั๊กการทำงานพร้อมกันและ advisory lock (Concurrency & Advisory Locks)
- กระบวนการสร้างสมุดรายวัน GL ใหม่ (GL Rebuild Process)
ที่มาในโค้ด
backend/internal/application/process/service.go:992lockStockItems(ค่าคงที่stockRebuildLockบรรทัด 990; exclusive ที่:1014)backend/internal/application/process/service.go:1297RebuildGLล็อก exclusive /:1365glRebuildLockพร้อมคอมเมนต์ข้อจำกัด /:1370lockGLSharedbackend/internal/application/process/trans.go:473lockTransDoc/:510saveTransTx(เรียกlockStockItemsบรรทัด 515)backend/internal/application/process/trans.go:1256rejectDuplicate"เลขที่เอกสารซ้ำ"backend/internal/application/process/doc_cancel.go:433ล็อกเลขเอกสารต่อ prefixbackend/internal/application/process/pay_check.go:170ล็อกบิลเรียงลำดับbackend/internal/application/process/service_test.go:58TestSavesTakeGLRebuildLockFirst