ข้ามไปเนื้อหาหลัก
ติดต่อ Social Plus SystemLINE @plus7089-480-4880092-247-3486[email protected]
Web App · คู่มือใช้งานจริง

Double Booking เกิดจากอะไร? ออกแบบระบบจองอย่างไรไม่ให้สองคนได้คิวเดียวกัน

คิวเดียวแต่มีลูกค้าสองคนไม่ใช่เรื่องดวงซวยเสมอไป หลายครั้งคือ Race Condition ในระบบจอง บทความนี้อธิบายวิธีป้องกันตั้งแต่การ Hold คิวจนถึง Database Constraint และการทดสอบตอนคนกดพร้อมกัน

ภาพตัวอย่างเดิมสำหรับบทความเรื่องการป้องกัน Double Booking โดยไม่มีการสร้างไฟล์ภาพใหม่

คำตอบสั้น ๆ: Double Booking เกิดได้เมื่อคนสองคนเห็นคิวเดียวกันว่า “ว่าง” เกือบพร้อมกัน แล้วระบบยอมให้ทั้งคู่บันทึกการจองก่อนที่จะกันคิวนั้นให้คนใดคนหนึ่งอย่างเด็ดขาด ปัญหานี้มักเป็นเรื่อง Concurrency หรือ Race Condition ไม่ใช่แค่ผู้ใช้มือไวเกินไป และการแก้ที่จริงจังต้องอยู่ที่ Server, Transaction และฐานข้อมูล—not ปิดปุ่มแล้วภาวนาให้ไม่มีใครกดพร้อมกัน

ถ้าธุรกิจมีคิวเวลา ห้อง โต๊ะ รถ พนักงาน ที่นั่ง หรือทรัพยากรที่ขายได้จำกัด การออกแบบ ระบบ Booking และระบบจองออนไลน์ ควรกำหนดตั้งแต่ต้นว่า “เมื่อไรถือว่าคิวถูกจับจอง” และ “ชั้นไหนเป็นคนตัดสินสุดท้ายว่าคิวนี้ยังขายได้” เพราะคำตอบสองข้อนี้ส่งผลโดยตรงต่อการป้องกันการจองซ้ำ

Double Booking เกิดจากอะไรในทางระบบ

สถานการณ์คลาสสิกมีหน้าตาประมาณนี้: ลูกค้า A เปิดหน้าจอและเห็นเวลา 10:00 ว่าง ลูกค้า B ก็เห็น 10:00 ว่าง จากนั้นทั้งคู่กดยืนยันในช่วงเวลาใกล้กัน Server ของ A ตรวจฐานข้อมูลแล้วพบว่ายังไม่มี Booking ส่วน Server ของ B ก็ตรวจในเสี้ยวเวลาที่สถานะยังไม่ถูกเปลี่ยน และสุดท้ายทั้งสอง Request บันทึกข้อมูลผ่าน

จุดอันตรายคือขั้นตอน “เช็กว่าว่าง” กับ “บันทึกว่าจองแล้ว” ถ้าไม่ได้ถูกทำให้เป็นการตัดสินใจที่ป้องกันการชนกัน ผู้ใช้สองคนสามารถผ่านช่องว่างระหว่างสองขั้นตอนได้ นี่คือเหตุผลที่โค้ดประมาณ “SELECT ก่อน ถ้าไม่เจอค่อย INSERT” อาจดูสมเหตุผลตอนทดสอบคนเดียว แต่พอ Production มีคนกดพร้อมกันก็เริ่มมีเรื่องให้ฝ่ายบริการลูกค้าปวดหัว

ปิดปุ่มหลังคลิกช่วยได้ แต่ไม่ใช่เกราะชั้นสุดท้าย

หน้าเว็บควรป้องกันการกดซ้ำ เช่น Disable ปุ่มหลัง Submit, แสดง Loading และไม่ส่ง Form ซ้ำโดยไม่จำเป็น สิ่งเหล่านี้ดีต่อ UX และลด Request ที่เกิดจากคนคนเดียว แต่ไม่สามารถป้องกันผู้ใช้คนละเครื่อง Browser คนละ Session หรือระบบภายนอกที่เรียก API เข้ามาพร้อมกันได้

กติกาสำคัญคือ Client ช่วยลดเหตุ แต่ Server ต้องรับผิดชอบความถูกต้อง ต่อให้หน้าเว็บมี JavaScript สวยเพียงใด ผู้ใช้ก็อาจ Refresh, กด Back, เปิดสอง Tab หรือเจอเครือข่าย Retry ได้ ระบบจึงต้องถือว่าคำขออาจเข้ามาซ้ำหรือพร้อมกันได้เสมอ

5 ชั้นที่ช่วยป้องกัน Double Booking

1. กำหนด Slot หรือ Inventory ให้มีตัวตนชัดเจน

ก่อนป้องกันการจองซ้ำ ต้องตอบให้ได้ก่อนว่า “อะไรคือของที่ห้ามขายซ้ำ” เช่น `staff_id + date + start_time`, `room_id + check_in + check_out` หรือหมายเลขที่นั่งเฉพาะ ระบบที่ปล่อยให้คำว่า “คิว” เป็นเพียงข้อความอิสระจะบังคับความไม่ซ้ำได้ยากมาก

ในบางโมเดลสามารถใช้ Unique Constraint กับคอลัมน์หรือชุดคอลัมน์ที่นิยาม Slot ได้โดยตรง PostgreSQL อธิบายว่า Unique Constraint ใช้บังคับให้ค่าหรือชุดค่าที่กำหนดไม่ซ้ำกันในตาราง นั่นทำให้ฐานข้อมูลเป็นด่านสุดท้ายที่ปฏิเสธข้อมูลซ้ำ แทนที่จะฝากความหวังไว้กับ `if` ใน Application เพียงอย่างเดียว

2. ใช้ Transaction หรือ Lock เมื่อต้องแก้ข้อมูลเดียวกันพร้อมกัน

ถ้าการจองหนึ่งครั้งต้องอ่านสถานะ ตรวจเงื่อนไข ตัดจำนวนคงเหลือ และสร้าง Booking หลายขั้น การทำงานเหล่านี้ควรถูกออกแบบเป็น Transaction ที่ชัดเจน และบางกรณีต้องใช้ Row-level Lock หรือกลไกควบคุม Concurrency ของฐานข้อมูลให้ Request ที่ชนทรัพยากรเดียวกันถูกจัดลำดับอย่างเหมาะสม

PostgreSQL ระบุว่า Row-level Lock สามารถบล็อก Writer หรือ Locker อื่นที่ชนแถวเดียวกันได้จน Transaction จบ อย่างไรก็ตาม Lock ไม่ใช่ยาวิเศษ: ถ้าล็อกกว้างเกินไปหรือถือ Lock นานระหว่างรอผู้ใช้กรอกข้อมูล ระบบอาจช้าและเสี่ยง Deadlock ได้ จึงควรล็อกเฉพาะช่วง Server-side ที่สั้นและจำเป็น

3. แยก Available, Held และ Confirmed ออกจากกัน

ระบบที่ต้องรอชำระเงินไม่ควรกระโดดจาก “ว่าง” ไป “จองเสร็จ” แบบคิดว่าทุกคนจะจ่ายทันที ควรมีสถานะอย่างน้อยตามบริบท เช่น:

  • Available — ยังไม่มีใครถือสิทธิ์
  • Held — กันคิวชั่วคราวระหว่าง Checkout หรือชำระเงิน
  • Confirmed — ผ่านเงื่อนไขยืนยันแล้ว
  • Expired — Hold หมดเวลาและคืนคิว
  • Cancelled — ยกเลิกตามกติกาและคืน Inventory เมื่ออนุญาต

Hold ต้องมี เวลา Expire หรือ TTL และมี Process ปล่อยคิวกลับอย่างแน่นอน ไม่อย่างนั้นระบบจะเปลี่ยนจาก “ขายซ้ำ” เป็น “ขายไม่ได้เพราะคิวผีล็อกไว้” ซึ่งก็ไม่ได้ทำให้ฝ่ายขายอารมณ์ดีขึ้นเท่าไร

4. ใช้ Idempotency กัน Request เดิมถูกทำซ้ำ

Double Booking ไม่ได้มาจากคนสองคนเสมอไป บางครั้ง Request เดิมถูกส่งซ้ำจากการกดปุ่มซ้ำ Mobile App Retry, Timeout หรือ Payment callback ที่เข้ามามากกว่าหนึ่งครั้ง ระบบที่มี Endpoint สร้าง Booking หรือยืนยันการชำระเงินจึงควรพิจารณา Idempotency Key หรือกลไกเทียบเท่า

แนวคิดคือ Request เดิมที่มี Key เดิมควรให้ผลลัพธ์เดิมแทนการสร้าง Booking ใหม่ซ้ำ Stripe ใช้แนวทางนี้ใน API เพื่อให้การ Retry ไม่ทำ Operation เดิมสองครั้ง แต่ต้องเข้าใจว่า Idempotency แก้ “Request เดิมซ้ำ” ไม่ได้แทน Constraint/Lock สำหรับ “คนละ Request แย่ง Slot เดียวกัน”

ถ้าระบบจองต้องคุยกับ Payment Gateway, CRM, LINE OA หรือระบบภายนอกหลายตัว ควรกำหนด Contract ของ Retry, Timeout, Duplicate callback และ Error state ให้ชัดตั้งแต่ก่อนเชื่อมต่อ อ่านแนวคิดต่อได้ในบทความ ก่อนเชื่อม API ต้องเตรียมอะไร

5. ให้ Database เป็นคนปฏิเสธสถานะที่ธุรกิจยอมรับไม่ได้

Application Validation มีไว้ให้ข้อความ Error อ่านง่ายและ Workflow เป็นมิตร แต่ถ้าธุรกิจมีกฎว่า “หนึ่ง Slot มี Confirmed Booking ได้เพียงหนึ่งรายการ” กฎนี้ควรถูกสะท้อนใน Data Model ด้วยวิธีที่ฐานข้อมูลบังคับได้เมื่อเหมาะสม เช่น Unique Constraint, Exclusion Constraint หรือโครงสร้าง Inventory ที่ตัดสิทธิ์แบบ Atomic ตามเทคโนโลยีที่ใช้

เหตุผลตรงไปตรงมา: Application อาจมีหลาย Instance, Worker, API หรือ Job ทำงานพร้อมกัน แต่ฐานข้อมูลคือจุดที่ทุกฝ่ายต้องมาจบเรื่องข้อมูลเดียวกัน การมีด่านบังคับที่ Data Layer ช่วยลดโอกาสที่ Bug ในเส้นทางหนึ่งจะสร้างข้อมูลที่ธุรกิจรับไม่ได้

ระบบจองที่มีการชำระเงิน ต้องตัดสินว่า “จองสำเร็จ” ตอนไหน

นี่เป็นคำถามทางธุรกิจพอ ๆ กับคำถามทางเทคนิค บางธุรกิจถือว่าจองสำเร็จทันทีเมื่อกดยืนยันและให้จ่ายภายหลัง บางธุรกิจต้องชำระครบก่อน บางธุรกิจเก็บมัดจำ ดังนั้น State Machine ต้องสะท้อนกติกาจริง ไม่ใช่ให้ Developer เดาเอาว่า Payment success น่าจะเท่ากับ Booking confirmed เสมอ

สิ่งที่ควรกำหนดเป็นลายลักษณ์อักษร ได้แก่ ระยะเวลา Hold, เงื่อนไขต่ออายุ Hold, วิธีรับมือ Payment pending, callback มาช้า, callback ซ้ำ, จ่ายเงินสำเร็จหลัง Hold หมดอายุ, การ Refund และลำดับเหตุการณ์เมื่อ Admin แก้ Booking เอง การกำหนดก่อนพัฒนาจะถูกกว่าการมานั่งถกกันหลังมีลูกค้าสองคนถือหลักฐานว่า “ระบบยืนยันแล้วทั้งคู่”

ตัวอย่าง Flow ที่ลดโอกาสจองคิวซ้ำ

  1. ผู้ใช้เลือก Slot และส่งคำขอ Hold
  2. Server เปิด Transaction และตรวจสถานะจาก Source of Truth
  3. ถ้า Slot ยังรับได้ ให้สร้าง Hold ที่มี `expires_at` และบันทึกโดยมี Constraint/Lock ตามโมเดลข้อมูล
  4. ถ้ามี Request อื่นชน Slot เดียวกัน ให้ได้ผลลัพธ์ “ไม่ว่างแล้ว” แทนการสร้าง Hold ที่สอง
  5. ผู้ใช้ไปขั้นชำระเงินโดยอ้างอิง Booking/Hold ID เดิม
  6. เมื่อรับผลชำระเงิน ให้ Process แบบ Idempotent เพื่อไม่ Confirm ซ้ำจาก Callback เดิม
  7. Confirm Booking หรือ Expire Hold ตาม State Transition ที่กำหนด

Flow จริงอาจต่างตามธุรกิจ แต่หลักสำคัญคือทุก Transition ต้องรู้ว่า ใครมีสิทธิ์เปลี่ยนสถานะ จากอะไร ไปอะไร และเปลี่ยนซ้ำได้หรือไม่ ถ้าตอบไม่ได้ Log ตอนเกิดปัญหาจะกลายเป็นนิยายสืบสวนที่ไม่มีใครอยากอ่าน

ก่อนขึ้น Production ต้องทดสอบ “กดพร้อมกัน” จริง

ระบบจองที่ผ่านการทดสอบแบบเปิด Browser หนึ่งหน้าต่าง กดทีละขั้น และทุกอย่างสำเร็จ ไม่ได้พิสูจน์ว่าป้องกัน Double Booking ได้ ควรมี Test Case ที่สร้าง Concurrency โดยตั้งใจ เช่น ส่งหลาย Request ไป Slot เดียวกันในช่วงใกล้กัน แล้วตรวจว่ามี Booking ที่ชนะตามกติกาเพียงรายการเดียว

รายการที่ควรทดสอบอย่างน้อย:

  • สองผู้ใช้ Hold Slot เดียวกันพร้อมกัน
  • ผู้ใช้คนเดียวกด Submit ซ้ำหรือ App Retry Request เดิม
  • Payment callback เดิมเข้าซ้ำ
  • Payment สำเร็จหลัง Hold หมดอายุ
  • Hold หมดอายุพร้อมกับมี Request ใหม่เข้ามา
  • Admin ยกเลิก Booking ขณะที่ลูกค้ากำลังชำระเงิน
  • ระบบภายนอก Timeout แต่ฝั่ง Server ทำรายการสำเร็จแล้ว

กรณีเหล่านี้ควรถูกรวมใน UAT Web App ก่อนขึ้น Production โดยกำหนด Expected Result ให้ชัดว่าใครต้องสำเร็จ ใครต้องถูกปฏิเสธ สถานะสุดท้ายเป็นอะไร และมีหลักฐานใน Log หรือ Audit Trail อย่างไร

Checklist สำหรับทีมธุรกิจก่อนพัฒนาระบบจอง

  • นิยาม Slot/Inventory ที่ห้ามขายซ้ำได้ชัดหรือยัง
  • หนึ่ง Slot รับได้กี่ Booking และนับตามสถานะใด
  • ต้อง Hold ก่อน Confirm หรือไม่ และ Hold นานเท่าไร
  • Payment pending, failed, timeout และ late success จัดการอย่างไร
  • Cancel แล้วคืน Slot ทันทีหรือมีเงื่อนไข
  • Request ซ้ำและ Callback ซ้ำต้องได้ผลลัพธ์แบบไหน
  • มี Constraint/Transaction strategy ที่ Data Layer หรือยัง
  • มี Audit Log พอไล่เหตุการณ์เมื่อเกิดข้อพิพาทหรือไม่
  • UAT มี Concurrent Test ไม่ใช่เฉพาะ Happy Path หรือยัง

ถ้าคำตอบหลายข้อยังเป็น “เดี๋ยวค่อยคิดตอนทำ” แปลว่า Requirement ของระบบยังขาดส่วนที่เสี่ยงที่สุดอยู่ การพัฒนา Web App และระบบหลังบ้าน ที่เกี่ยวกับคิวหรือ Inventory ควรล็อกกติกา State และ Concurrency ก่อนลงรายละเอียดหน้าจอ เพราะ UI สวยช่วยไม่ได้ถ้าฐานข้อมูลบอกลูกค้าสองคนว่าได้โต๊ะเดียวกัน

สรุป: กัน Double Booking ต้องออกแบบเป็นระบบ ไม่ใช่ Patch หลังเกิดเรื่อง

Double Booking เกิดจากอะไร? แก่นของปัญหาคือระบบไม่มีจุดตัดสินที่รับมือคำขอพร้อมกันอย่างแน่นอน หรือไม่มีการป้องกัน Request เดิมถูกทำซ้ำ วิธีแก้จึงควรใช้หลายชั้นร่วมกันตามความเหมาะสม: กำหนด Slot ให้ชัด, ใช้ Transaction/Lock, มี Database Constraint, แยก Hold/Confirm/Expire, ใช้ Idempotency และทดสอบ Concurrency ก่อนเปิดจริง

สำหรับธุรกิจ ประเด็นสำคัญไม่ใช่เลือกศัพท์เทคนิคให้ครบ แต่ต้องกำหนดกติกาว่า “ใครได้สิทธิ์ เมื่อไร และถ้าขั้นตอนล้มเหลวจะคืนสิทธิ์อย่างไร” ให้ชัดก่อนพัฒนา จากนั้นทีมเทคนิคจึงแปลงกติกาเหล่านั้นเป็น Data Model และ Workflow ที่ป้องกันการขายคิวเดียวกันสองรอบได้จริง

แหล่งข้อมูล

แหล่งอ้างอิง

  1. PostgreSQL Global Development Group. PostgreSQL Documentation — Constraints (2026). เข้าถึงวันที่ 2026-08-29.
  2. PostgreSQL Global Development Group. PostgreSQL Documentation — Explicit Locking (2026). เข้าถึงวันที่ 2026-08-29.
  3. Stripe. Idempotent requests (2026). เข้าถึงวันที่ 2026-08-29.

คำถามที่พบบ่อยเกี่ยวกับ Double Booking เกิดจากอะไร

Double Booking เกิดจากอะไร

Double Booking เกิดได้เมื่อผู้ใช้สองคนหรือสอง Request ตรวจพบว่าคิวเดียวกันยังว่างในเวลาใกล้กัน แล้วต่างฝ่ายต่างสร้างการจองสำเร็จ ปัญหานี้เรียกว่า Race Condition ได้ในหลายกรณี และต้องป้องกันที่ฝั่ง Server/Database ไม่ใช่อาศัยแค่การปิดปุ่มบนหน้าเว็บ

แค่เช็กว่าคิวยังว่างก่อนบันทึก พอป้องกันการจองซ้ำไหม

ยังไม่พอ หากการเช็กและการบันทึกเป็นคนละจังหวะ Request อื่นอาจแทรกเข้ามาระหว่างนั้นได้ วิธีที่ปลอดภัยกว่าคือออกแบบ Transaction หรือ Lock ให้เหมาะกับข้อมูล และใช้ Constraint ในฐานข้อมูลเป็นชั้นบังคับสุดท้ายตามรูปแบบ Slot ของระบบ

ระบบจองที่มีการชำระเงินควรล็อกคิวนานเท่าไร

ไม่มีเวลามาตรฐานเดียวสำหรับทุกธุรกิจ ควรกำหนด Hold TTL จากเวลาที่ผู้ใช้ต้องใช้จริง ความเร็วของช่องทางชำระเงิน และผลกระทบต่อ Inventory หากล็อกนานเกินไป พร้อมมีกติกา Expire และปล่อยคิวอัตโนมัติที่ชัดเจน

Idempotency ช่วยป้องกัน Double Booking อย่างไร

Idempotency ช่วยให้ Request เดิมที่ถูกส่งซ้ำ เช่น ผู้ใช้กดซ้ำ แอป Retry หรือเครือข่ายขาดช่วง ไม่สร้างผลลัพธ์ซ้ำหลายครั้ง แต่ไม่ได้แทนการควบคุมกรณีผู้ใช้คนละคนแย่ง Slot เดียวกัน จึงมักต้องใช้ร่วมกับ Transaction, Lock หรือ Database Constraint

ควรทดสอบระบบจองแบบไหนก่อนเปิด Production

ควรมี Test Case ที่ยิงหลาย Request ไปยัง Slot เดียวกันใกล้พร้อมกัน แล้วตรวจว่ามีผู้ชนะเพียงหนึ่งรายตามกติกา รวมถึงทดสอบกดซ้ำ, Retry, Payment timeout, callback ซ้ำ, Hold หมดอายุ, ยกเลิก และการคืน Slot ก่อน Sign-off UAT

ต้องการนำแนวทางนี้ไปใช้กับธุรกิจจริง?

ดูขอบเขตบริการที่เกี่ยวข้อง วิธีทำงาน และข้อมูลที่ควรเตรียมก่อนให้ทีมประเมิน

ดูบริการที่เกี่ยวข้อง
PROJECT ASSESSMENT

มีโจทย์คล้ายบทความนี้? ส่งข้อมูลให้ทีมช่วยดูได้

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

บทความที่เกี่ยวข้อง

ก่อนเชื่อม API ธุรกิจต้องเตรียมอะไรบ้าง? Checklist ก่อนเริ่ม Automation

การเชื่อม API ไม่ได้เริ่มจากการขอ API Key อย่างเดียว ธุรกิจควรเตรียมระบบต้นทาง-ปลายทาง โครงสร้างข้อมูล สิทธิ์ ตัวอย่างข้อมูล เงื่อนไข Error และผู้รับผิดชอบให้ชัดก่อนเริ่ม Automation

UAT Web App ก่อนขึ้น Production ต้องเช็กอะไร? อย่าเปิดจริงแล้วค่อยพบว่าปุ่มอนุมัติหาย

ถ้าแผน UAT คือเปิดระบบแล้วบอกทุกคนว่า “ลองกดดูนะ” นั่นยังไม่ใช่การตรวจรับระบบ บทความนี้ช่วยจัด UAT ให้เป็น Test Case ที่รู้ว่าใครทดสอบอะไร ต้องได้ผลแบบไหน และ Bug ระดับไหนห้ามพาขึ้น Production

Soft Delete คืออะไร? ระบบหลังบ้านควรใช้ Archive หรือลบถาวรเมื่อไร

ปุ่ม “ลบ” ในระบบหลังบ้านไม่ควรหมายถึงสิ่งเดียวทุกกรณี บทความนี้แยก Soft Delete, Archive และ Delete ถาวร พร้อมเกณฑ์ตัดสินใจและ Test Case ที่ควรกำหนดก่อนพัฒนา

สิทธิ์ผู้ใช้ระบบหลังบ้านควรวางอย่างไร? ทำ Permission Matrix ก่อนพัฒนา Web App

คำว่า Admin กับ Staff ยังไม่พอสำหรับระบบจริง บทความนี้ให้ Framework และ Permission Matrix สำหรับกำหนดว่าใครดู แก้ อนุมัติ Export ลบ และจัดการผู้ใช้ได้แค่ไหนก่อนเริ่มพัฒนา

โทร 089480-4880 โทร 092247-3486 LINE@plus7 อีเมลส่งข้อความ