Concurrency Control

เมื่อหลาย client เขียน/อ่านข้อมูลเดียวกันพร้อมกัน — ต้องมีกลไกให้ผลลัพธ์ถูกต้อง

Shared Lock vs Exclusive Lock

Lock Typeใช้เมื่อกฎ
Shared (S) — Read lockต้องการอ่านโดยค่าไม่เปลี่ยนหลาย tx ถือ S พร้อมกันได้ แต่ห้ามมี X
Exclusive (X) — Write lockต้องการ updateถือได้แค่ตัวเดียว ห้ามมี S หรือ X อื่น

Deadlock

2 transactions ต่างถือ lock ที่อีกฝ่ายต้องการ → ทุกคนรอกัน → block ไม่มีวันจบ

-- TX1                          TX2
BEGIN;                           BEGIN;
UPDATE A SET x=1 WHERE id=1;     UPDATE A SET x=2 WHERE id=2;
-- ถือ X-lock บน id=1            -- ถือ X-lock บน id=2
UPDATE A SET x=3 WHERE id=2;     UPDATE A SET x=4 WHERE id=1;
-- รอ TX2 ปล่อย id=2              -- รอ TX1 ปล่อย id=1
-- DEADLOCK!

DB จัดการ: Postgres/MySQL มี deadlock detector → rollback transaction ที่เข้ามาทีหลัง → app ต้องมี retry logic

วิธีลด deadlock: เข้าถึง resources ตามลำดับเดียวกันเสมอ (เช่น lock ID น้อยก่อน)

Two-Phase Locking (2PL)

ป้องกัน anomaly จากการ release lock ระหว่าง transaction:

  1. Growing phase — ขอ lock ได้เรื่อย ๆ แต่ห้าม release
  2. Shrinking phase — release lock ได้ แต่ห้ามขอใหม่
-- With 2PL (ปลอดภัย)
BEGIN;
SELECT * FROM accounts WHERE id=1 FOR UPDATE;  -- ขอ X-lock
UPDATE accounts SET balance=...;
COMMIT;  -- release ทุก lock พร้อมกัน

Double Booking Problem

ผู้ใช้ A และ B เห็นที่ว่างเดียวกัน → กดจองพร้อมกัน → ทั้งคู่สำเร็จ → ที่ซ้ำ!

3 วิธีแก้

(1) Pessimistic — SELECT FOR UPDATE

BEGIN;
SELECT * FROM seats WHERE id=42 FOR UPDATE;  -- lock จน commit
INSERT INTO bookings ...;
UPDATE seats SET status='taken' WHERE id=42;
COMMIT;

ปลอดภัยสุด แต่ block ผู้อื่น = ช้า

(2) Optimistic — Version Column

UPDATE seats SET status='taken', version=version+1 
  WHERE id=42 AND version=<old_version>;
-- affected rows = 0 → มีคนแก้ก่อน → retry

(3) Database Constraint — Unique Index (แนะนำ)

CREATE UNIQUE INDEX uniq_seat_booking ON bookings(seat_id);
-- DB ปฏิเสธ 1 คนอัตโนมัติ — เรียบง่ายและถูกต้องที่สุด

SELECT FOR UPDATE Variants (Postgres)

Modeความหมาย
FOR UPDATEX-lock — ห้ามทั้ง read และ write
FOR NO KEY UPDATEX-lock อ่อนกว่า — อนุญาต FK reference
FOR SHARES-lock — อ่านได้ ห้าม update
FOR KEY SHARES-lock เฉพาะ key columns

Lock Levels ใน Postgres

Table-level locks (อ่อนไปแรง):

  • ACCESS SHARE (SELECT)
  • ROW SHARE (SELECT FOR SHARE)
  • ROW EXCLUSIVE (UPDATE/DELETE/INSERT)
  • SHARE UPDATE EXCLUSIVE (VACUUM, CREATE INDEX CONCURRENTLY)
  • SHARE (CREATE INDEX)
  • ACCESS EXCLUSIVE (ALTER TABLE, DROP TABLE)

Pitfall: ALTER TABLE ADD COLUMN ... DEFAULT ... ใน Postgres เก่ากว่า 11 → rewrite ทุก row → ACCESS EXCLUSIVE lock = block ทุก SELECT — ใน PG 11+ มี optimization แล้ว

Key Points

  • Shared lock = อ่าน (หลายคนถือได้) / Exclusive lock = เขียน (ถือคนเดียว)
  • Deadlock — DB detect และ rollback tx ที่มาทีหลัง → app ต้อง retry
  • 2PL — growing + shrinking phase ป้องกัน anomaly
  • Double Booking — แก้ด้วย unique constraint ง่ายสุด
  • เข้าถึง resources ตามลำดับเดียวกันเสมอ เพื่อลด deadlock