ข้ามไปยังเนื้อหา

Transactions & Concurrency

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

transaction คือเครื่องมือสำหรับสิ่งนี้โดยเฉพาะ โดยจัดกลุ่มคำสั่งตั้งแต่หนึ่งคำสั่งขึ้นไปให้เป็นหน่วยงานเดียวแบบ all-or-nothing ถ้าไม่ทุกคำสั่งมีผล ก็ไม่มีคำสั่งไหนมีผลเลย นอกจากนี้ ฐานข้อมูลที่มีงานหนาแน่นยังมี transaction หลายตัวทำงานพร้อมกันในเวลาเดียวกัน ซึ่งนี่คือสิ่งที่เราหมายถึงคำว่า concurrency โมดูลนี้พูดถึงทั้งสองเรื่อง คือวิธีนิยามหน่วยงาน และวิธีที่ PostgreSQL ป้องกันไม่ให้ transaction เหล่านั้นทำให้ข้อมูลของกันและกันเสียหาย

transaction ใน PostgreSQL ให้การรับประกันแก่คุณสี่ข้อ จำง่าย ๆ ด้วยตัวย่อ ACID

ตัวอักษรคุณสมบัติสิ่งที่รับประกัน
AAtomicityทุกคำสั่ง commit ไปด้วยกันหรือ roll back ไปด้วยกัน ไม่มีทางทำเสร็จครึ่งเดียว
CConsistencyฐานข้อมูลเคลื่อนจากสถานะที่ถูกต้องหนึ่งไปยังอีกสถานะที่ถูกต้อง โดยเคารพ constraint
IIsolationtransaction ที่ทำงานพร้อมกันจะไม่เห็นงานที่ยังไม่เสร็จของกันและกัน
DDurabilityเมื่อ commit แล้ว การเปลี่ยนแปลงจะอยู่รอดผ่านการ crash หรือไฟดับ

Atomicity คือส่วนที่คุณรู้สึกได้ตรง ๆ ที่สุดเมื่อพิมพ์ COMMIT หรือ ROLLBACK ส่วน Isolation คือส่วนที่เริ่มน่าสนใจเมื่อมี client มากกว่าหนึ่งตัวเชื่อมต่ออยู่ และเป็นจุดที่เนื้อหาเชิงลึกส่วนใหญ่ของโมดูลนี้อยู่

สี่บทเรียนถัดไปต่อยอดกันเป็นชั้น คุณเริ่ม transaction คุณเลือกว่า transaction นั้นควร isolate มากแค่ไหน และเบื้องล่างของทั้งสองสิ่งนั้น PostgreSQL ใช้กลไกสองอย่างเพื่อให้ concurrency ปลอดภัย คือการเก็บ row เดียวกันไว้หลายเวอร์ชัน และการจับ lock เมื่อจำเป็น

flowchart TD
  A[Transaction: BEGIN to COMMIT] --> B[Isolation level: how much it sees]
  B --> C[MVCC: each transaction reads a snapshot]
  B --> D[Locking: serialize conflicting writes]
  C --> E[Safe concurrency for many clients]
  D --> E
จากหนึ่ง transaction สู่ concurrency ที่ปลอดภัย
  • Transaction basicsBEGIN, COMMIT, ROLLBACK, atomicity และ SAVEPOINT สำหรับการ roll back บางส่วน สาธิตด้วยการโอนเงินระหว่างบัญชี
  • Isolation levels — Read Committed, Repeatable Read และ Serializable พร้อม read anomaly ที่แต่ละระดับป้องกันได้
  • MVCC — Multi-Version Concurrency Control ที่ writer ไม่ block reader และ reader ไม่ block writer เพราะแต่ละ transaction อ่านจาก snapshot ที่สอดคล้องกัน
  • Locking and deadlocks — row lock แบบชัดเจนด้วย SELECT ... FOR UPDATE, เหตุที่สอง transaction เกิด deadlock ได้ และวิธีที่ PostgreSQL ตัดสินผลเสมอ

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

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

ใน pgAdmin: โดยค่าเริ่มต้น Query Tool จะรันแต่ละคำสั่งใน transaction แบบ auto-commit ของตัวเอง หากต้องการจัดกลุ่มคำสั่ง ให้พิมพ์ BEGIN; ด้วยตัวเองที่ด้านบนและ COMMIT; ที่ท้าย แล้วรันทั้งบล็อกพร้อมกันทีเดียว

  • connection ที่ไม่ได้อยู่ภายใน transaction แบบชัดเจนจะรันแต่ละคำสั่งใน transaction โดยปริยายของตัวเอง และ commit ทันที สิ่งนี้เรียกว่า auto-commit
  • isolation คือสเปกตรัม ไม่ใช่สวิตช์ ระดับที่สูงขึ้นป้องกัน anomaly ได้มากขึ้น แต่ทำให้ conflict มีโอกาส abort มากขึ้นด้วย
  • PostgreSQL ไม่เคยขอให้ reader รอ writer สำหรับ query SELECT ธรรมดา นั่นคือประโยชน์เด่นของ MVCC
  • deadlock เป็นความเป็นไปได้ปกติในระบบ concurrent ใด ๆ ไม่ใช่บั๊ก แอปพลิเคชันที่เขียนดีจะเผื่อไว้แล้ว retry
ตัว "A" ใน ACID รับประกันอะไรเกี่ยวกับ transaction?
"concurrency" หมายถึงอะไรในบริบทของ database?
ACID property ตัวไหนที่อธิบายว่า transaction ที่รันพร้อมกันจะมองไม่เห็นงานที่ยังไม่เสร็จของกันและกัน?