Concurrency Part 2 - Transaction , ACID และ Isolation level ของ Relational Database

Concurrency Part 2 : Transaction , ACID และ Isolation level ของ Relational Database

ผมเขียนเกี่ยวกับ Concurrency ไว้หลายตอน คุณสามารถกด Link ด้านล่างเพื่ออ่านที่เกี่ยวกับ Concurrency ตอนต่างๆได้เลย

ตอนที่แล้วเราพูดถึงเรื่องปัญหาการใช้ Database พร้อมกันจากหลาย Process (Thread, Request) ซึ่งทำให้เกิดปัญหา Lost update, Dirty read, Non repeatable read ซึ่งจริงๆมีอีกหลายตัว แต่พูดตัวที่เห็นง่ายๆก่อน ในตอนนี้เรามารู้จักกับศัพท์ที่จะได้ยินบ่อยต่อจากนี้ และจะเจอบ่อยเวลาทำงาน

Transaction

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

ถ้าสมมุติเราจะโอนเงินจากบัญชี A จำนวนเงิน 100 ไปบัญชี B การทำงานจะเป็นดังภาพด้านซ้าย แต่นั่นคือกรณีที่เราโชคดีครับโลกมียังมีเรื่องโชคร้ายอีกเยอะแยะที่จะเกิดขึ้น (จริงๆไม่ใช่โชคร้ายอาจเป็นเหตุการณ์บางอย่างที่มันเกิดขึ้นได้แล้วพอมันเกิดขึ้นแล้วไม่ส่งผลดีต่อเราเราเลยบอกว่ามันเป็นโชคร้าย) ในภาพด้านขวานั้นเกิด Database ดับไปซึ่งนั่นแปลว่าเงินที่ต้องไปเพิ่มในบัญชี B นั้นหายไปซึ่งจะทำยังไงต่อล่ะ คนเขียน App จะต้องทำยังไงต้องเก็บข้อมูลไว้ก่อนเหรอ แล้วจะรู้เหรอว่า Database ทำงานไปถึงไหนแล้ว

ด้วยปัญหานี้ที่เกิดขึ้นจึงมีการนิยาม Transaction ขึ้นมาเพื่อให้คนเขียน Database อ่าเรียกมันว่า DBMS (Database Management System) จัดการปัญหานี้ให้กับผู้ใช้งาน DBMS สามารถใช้งานได้โดยไม่ต้องหาวิธีมาจัดการปัญหานี้ ซึ่งโชคดีที่ RDBMS ส่วนใหญ่ดังๆในตอนนี้เช่น MySQL, MariamDB, SQLServer, Oracle, Ibm Db2 รองรับการมี Transaction นี้

คำถาม : อ้าวแล้วที่เรา Update กันอยู่นี้มันเป็น Transaction ไหม

คำตอบคือเป็นจ้า คือถ้าเราสั่ง Update คำสั่งเดียวๆเฉยๆนั้นก็เป็น Transaction เพราะ DBMS จะเข้าใจว่าทุกการเปลี่ยนแปลง DBMS เป็น Transaction ดังนั้นตัวอย่างด้านบน DBMS ไม่ถือว่าทำงานผิดนะเพราะเขาถือว่า Update Account A เป็น 1 transaction แล้วมันทำงานสำเร็จมันก่อน DBMS ดับ ดังนั้นมันถือว่า DBMS ไปอยู่ใน state ที่ถูกต้องแล้ว ตรงนี้อยากอธิบายให้เข้าใจว่า DBMS เขาไม่รู้ว่า Logic ว่าต้อง Update Account B ก่อนถึงจะถูกต้องตามกฏ

คำถาม : แล้วจะทำ Transaction กับหลายๆคำสั่งยังไง แล้วเราจะบอกให้ DBMS ว่า Transaction มันสำเร็จหรือไม่สำเร็จ

คำตอบคือ แค่สั่งเองจ้าตัวคนเขียน DBMS เขากำหนดคำสั่งให้เราแล้วโดยคำสั่งที่ว่านั้น Begin , Commit , Abort (บาง DBMS อาจเรียกแตกต่างกันเช่น Begin บางที่เรียก Set auto commit off , Abort เรียก Rollback)

ดังนั้นการสั่งโอนเงินของเราจากภาพเราต้องสั่งเป็นดังภาพ คือเราจะบอกกับ DBMS ก่อนว่าคำสั่งอะไรต่างๆต่อนี้จะเป็น Transaction นะ เมื่อ DBMS ทราบจากนั้นเราสั่งคำสั่ง SQL ต่อจากนั้นจะถือว่าเป็น Transaction ทั้งหมด

กรณีทุกอย่างทำงานถูกต้อง (ภาพด้านซ้าย)

ถ้าทุกอย่างทำงานได้อย่างถูกต้องแล้วเราอยากบอกให้ DBMS ทราบว่าเรายืนยันว่า Transaction นี้ทำงานถูกต้อง เราแค่สั่ง Commit ไปที่ DBMS เมื่อสั่งไปที่ DBMS ตัว DBMS จะมีวิธีการทำงานบางอย่างเพื่อให้ข้อมูลมันคงอยู่ (เขามีวิธีของเขา ซึ่งมีหลากหลายวิธี แต่ละวิธีมีความเร็วความช้าไม่เหมือนกัน ) ซึ่งถ้า DBMS ตอบกลับมาว่าสำเร็จ ขอให้เราเชื่อใจได้เลยว่าข้อมูลจะถูกบันทึกไว้แล้ว แม้ว่า DBMS จะดับไปหลังจากได้รับตอบกลับแค่ 1 ns หลังจากนั้น (ยกเว้นที่เก็บข้อมูล Database จะระเบิดแล้วไม่ได้ทำ Stable storage ไว้)

กรณี DBMS ดับไปแล้วไม่ได้ Commit (ภาพด้านขวา)

ในกรณีนี้ไม่มีอะไรมากครับถ้าเราไม่ได้ Commit แล้วตัว DBMS เกิดดับเมื่อ Start ตัว DBMS ขึ้นมาตัว DBMS จะมีวิธีจัดการของเขาเองแล้วเขารับประกันว่าอะไรข้อมูลของเราที่ถูกเก็บไว้จะไปอยู่ใน state ก่อนเริ่ม Transaction

กรณีเราอยากยกเลิก Transaction (ภาพตรงกลาง)

ในบางครั้งเราอาจจะอยากยกเลิก Transaction ที่ทำงานไปแล้วเช่น เรามาทราบทีหลังว่าบัญชี B อะเป็นบัญชีที่ใช้ฟอกเงิน (เอ้าแล้วทำไมมึงไม่ตรวจก่อนวะ อันนี้ผมก็ไม่รู้ สมมุติเอาว่าเป็นแบบนั้น) เราจะจึงไม่อยากจะทำงานต่อ แล้วจะมาย้อนหลังอัพเดทค่าที่เคยอัพไปแล้วก็ลำบาก (ทำได้แหละแต่ถ้าระหว่างย้อนมันเกิดปัญหาอีกล่ะ มันจะซ้อนของซ้อนของซ้อนไปเรื่อยๆ) อย่ากระนั้นเลยไหนๆก็มี Feature transaction แล้ว สั่งยกเลิก Transaction ซะ ข้อมูลทุกอย่างก็จะกลับไปอยู่ใน state ก่อนเริ่ม Transaction

ACID คืออะไรล่ะ

ACID คือคุณสมบัติของ Transaction นั่นเองโดย ACID เป็นคำย่อตามภาพ (จริงๆมีคำอธิบายในภาพแล้ว)

ภาพจากเว็บ https://www.geeksforgeeks.org/acid-properties-in-dbms/

Atomicity

คือเป็นอันหนึ่งเดียวกันประมาณว่าทุกคำสั่งใน Transaction จะเสมือนว่าเป็นคำสั่งเดียว ถ้าสำเร็จ (commit) คือสำเร็จทั้งหมด ถ้าไม่สำเร็จ (abort) คือไม่สำเร็จทั้งหมด

Consistency

คือความถูกต้อง ถ้า Transaction ถูกสั่งให้ Commit จะถือว่าที่สั่งคำสั่งทั้งหมดใน Tranaction ทำงานสำเร็จ (ย้ายจาก state นึงไปอีก state นึงเรียบร้อย) แต่ถ้า Transaction ถูกสั่งให้ Abort หรือ DBMS เกิดปัญหาก่อน Commit ทุกสิ่งที่ทำใน Transaction จะต้องเหมือนไม่เกิดขึ้น (ข้อมูลจะต้องกลับไปอยู่ใน state )

Isolation

อันนี้คือ…. แปลยากจริงๆ อันนี้คือความโดดเดี่ยว ประมาณว่า Transaction จะต้องทำงานโดยเสมือนว่าไม่มีอีก Transaction นึงทำงานอยู่อันนี้คือการทำงานที่ดีที่สุดซึ่งระดับความโดดเดี่ยวนั้นมีหลายระดับ (ก็เหมือนความเหงาที่มีหลายระดับเหมือนกัน) ซึ่งโดยทางปฏิบัตินั้นการกำหนด Isolation ไว้สูงนั้นจะลด Throughput ของระบบลง ดังนั้นการเลือกระดับ Isolation ก็มีความสำคัญเช่นกัน

Durability

คือความคงทน อันนี้ก็จะประมาณว่าถ้าสั่ง Commit ไปแล้ว ข้อมูลจะต้องถูกบันทึกแน่นอนไม่ว่าจะยังไงก็แล้วแต่

Isolation level

Isolation level คือระดับความโดดเดี่ยว (ยังกะความเหงา) ของ Transaction โดยใน Relational Database นั้นได้กำหนดมาตรฐานของ Isolation level ไว้ใน The ANSI/ISO standard SQL 92 ซึ่งมี 4 ระดับคือ

  1. Read uncommitted
  2. Read committed
  3. Repeatable reads
  4. Serializable

ก่อนจะพูดกันต่อนั้น ระดับของ Isolation level มันแค่บอกว่าต้องทำอะไรได้ถึงจะอยู่ในระดับ Isolation level นี้ได้ ไม่ได้บอกวิธีการ Implement ว่าต้อง Implement ยังไง อันนี้แล้วแต่ผู้พัฒนา DBMS เลยว่าจะทำยังไง ดังนั้นผมจะไม่พูดวิธี Implement ของมันละกัน (เพราะผมก็ไม่รู้ว่าจริงๆเขาทำกันแบบไหน)

Read uncommitted

Isolation level นี้ระดับนี้ก็ตามชื่อเลยครับคือสามารถอ่านข้อมูลที่ Transaction อื่นยังไม่ Commit ได้ซึ่งดูได้จากภาพ จะเห็นว่า Transaction ที่ 2 สามารถอ่านค่า Transaction ที่ 1 ได้แม้จะ Transaction ที่ 1 ยังไม่ Commit

Read committed

Isolation level นี้ก็ตามชื่ออีกเช่นกัน (ก็ไม่รู้จะอธิบายทำไม ฮ่าๆๆๆๆ) ก็คือ Transaction ที่อยู่ใน Isolation นี้จะอ่านได้เฉพาะค่าที่ commit แล้วเท่านั้น อะไรที่ยังไม่ commit จะไม่เห็น

พออ่านมาถึงตรงนี้ก็อ้าวเฮ้ยภาพข้างบนมันทำงานผิดนี่ ไม่ต้องตกใจครับ Isolation level แค่บอกเฉยๆว่าทำอะไรได้ ดังนั้นมันจึงไม่แปลกที่จะเกิดปัญหา

Repeatable reads

Isolation level นี้ก็ตามชื่ออีกเช่นกัน (เริ่มกำหมัดแล้วสินะ คือจริงๆคนตั้งชื่อ level เขาตั้งได้ดีมากกว่า) ก็คือ Transaction ที่อยู่ใน Isolation นี้จะอ่านค่าที่ Commit แล้วเท่านั้นและต้องได้ค่าเดิมเสมอ หรืออะไรที่ตัวเองทำการแก้ไขไปแล้ว (แม้จะไม่ได้อ่านก็ตาม) ซึ่งจากภาพจะเห็นว่าถ้าจะใช้รูปแบบการเข้ามาของข้อมูลเหมือนเดิมจะเกิดขึ้นได้สองกรณีคือ Tx1 (ย่อมาจาก Transaction 1) สามารถ Commit ได้ส่วน Tx2 Abort ได้ หรือ อีกกรณีนึง Tx2 สามารถ Commit Tx1 Abort (ขึ้นอยู่กับ DBMS เขา Implement ยังไง) ซึ่งที่เป็นอย่างงี้เพราะ Transaction ทั้ง 2 ตัวต่างขอ Isolation level เป็น Repeatable reads ดังนั้นเมื่อมีใครสักพยายามเขียนค่าแล้ว commit ซึ่งมันไปขัดกับหลักการที่ว่า Tx ต้องอ่านแล้วได้ค่าเดิมเสมอ ดังนั้นต้องมีใครคนใดคนหนึ่งต้องถูก Abort เพื่อรักษาความเป็น Repeatable read

Serializable

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

Isolation level แต่ละอันแก้ปัญหาอะไรได้บ้าง

ภาพจาก wikipedia

อันนี้มาถึงจุดสุดท้ายละคือจริงๆที่เล่ามาทั้งหมดเนี่ย ถ้าจะเอาไปใช้งานแบบโอเคกลัวว่าจะเกิดปัญหานี้ก็ใช้ Isolation Level นี้ไปเลยจะได้ไม่ต้องกลัวเกิดปัญหาอ่านแค่ส่วนนี้ส่วนเดียวตอบโจทย์ (ผมเลยเอามาไว้ล่าสุดยังไงล่ะ) ก็ถ้าสมมุติงานของเราอยากอ่านข้อมูลในระดับที่ Commit แล้วในช่วงเวลาที่กด ไม่ได้สนใจว่าหลังจากนั้นจะมีการแก้ไขไหม อันนี้เราสามารถใช้ Isolation level ระดับ Read commited ได้ งานพวกนี้ก็เช่น Report ย้อนหลัง แต่ถ้าเป็นงานพวกที่ให้ระดับความสำคัญมากๆ อาจเพิ่มเป็น Isolation level ระดับ Repeatable reads แทน

ตัดจบแค่นี้ก่อน

สำหรับตอนนี้ผมขอจบแค่นี้ก่อนเพราะดูเหมือนจะยาวมากแล้ว ในส่วนของตอนต่อไปจะมาอธิบายเกี่ยวกับปัญหา Phantom ที่ยังติดอยู่ว่าคืออะไร

Reference

https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-95-51.pdf

https://onlinelibrary.wiley.com/doi/abs/10.1002/(SICI)1097-024X(199801)28:1%3C77::AID-SPE148%3E3.0.CO;2-R

https://www.geeksforgeeks.org/acid-properties-in-dbms/

https://en.wikipedia.org/wiki/Isolation_(database_systems)

เพลงประกอบการเขียน Blog