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:
- Growing phase — ขอ lock ได้เรื่อย ๆ แต่ห้าม release
- 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 UPDATE | X-lock — ห้ามทั้ง read และ write |
FOR NO KEY UPDATE | X-lock อ่อนกว่า — อนุญาต FK reference |
FOR SHARE | S-lock — อ่านได้ ห้าม update |
FOR KEY SHARE | S-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 EXCLUSIVElock = 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
Related
- Transaction Isolation — isolation levels ที่ locks enforce
- ACID — concurrency control ทำให้ได้ Isolation + Consistency
- PostgreSQL — lock types, SELECT FOR UPDATE variants
- Database Replication — cross-replica consistency ต้อง sync