1. การทำธุรกรรมและความทันทีของข้อมูล
การทำธุรกรรม คือหน่วยตรรกะในกระบวนการดำเนินการของระบบจัดการฐานข้อมูล ประกอบด้วยชุดของการดำเนินการ การดำเนินการเหล่านี้จะสำเร็จหรือล้มเหลวพร้อมกัน และถูกจัดการเป็นอันจำนวนไม่แยกกัน ลักษณะสำคัญของการทำธุรกรรมสามารถสรุปได้เป็นรายละเอียดต่อไปนี้:
- Atomicity: การดำเนินการทั้งหมดในการทำธุรกรรมจะสามารถสมบูรณ์ทั้งหมดหรือล้มเหลวทั้งหมดเท่านั้น ไม่สามารถสมบูรณ์บางส่วนได้
- Consistency: การทำธุรกรรมจะต้องทำให้ฐานข้อมูลไปจากสถานะที่สอดคล้องกันไปสู่สถานะอื่นที่สอดคล้องกัน
- Isolation: การดำเนินการของการทำธุรกรรมจะต้องได้รับการป้องกันจากการรบกวนโดยการทำธุรกรรมอื่น ๆ และข้อมูลระหว่างการทำธุรกรรมพร้อมกันจะต้องได้รับการป้องกัน
- Durability: เมื่อการทำธุรกรรมได้รับการยืนยันแล้ว การปรับเปลี่ยนที่ทำโดยมันจะยังคงอยู่ในฐานข้อมูล
ความทันทีของข้อมูล หมายถึงการรักษาสถานะข้อมูลที่ถูกต้องและถูกต้องในฐานข้อมูลหลังจากชุดของการดำเนินการ ในสถานการณ์ที่เกี่ยวข้องกับการเข้าถึงพร้อมกันหรือข้อผิดพลาดของระบบ ความทันทีของข้อมูลนั้นมีความสำคัญมากโดยเฉพาะ และการทำธุรกรรมจะมอบหมายที่จะรักษาความทันทีของข้อมูลโดยไม่เสี่ยงเสียหายแม้ในกรณีของข้อผิดพลาดหรือความขัดแย้ง
2. ภาพรวมของเฟรมเวิร์ก ent
ent
เป็นเฟรมเวิร์กของตัวบุคคลภายในฐานข้อมูล ผ่านการสร้างรหัสในภาษาโก เฟรมเวิร์กนี้ให้ API ที่ปลอดภัยต่อชนิดสำหรับดำเนินการกับฐานข้อมูล สามารถทำให้การดำเนินการกับฐานข้อมูลเป็นเรื่องน่าเข้าใจและปลอดภัย โดยสามารถหลีกเลี่ยงปัญหาด้านความปลอดภัยอย่าง SQL injection ในเชิงการทำธุรกรรม ent
เฟรมเวิร์กยังมีการสนับสนุนที่แข็งแรงต่อการดำเนินการทางธุรกรรม ทำให้นักพัฒนาสามารถดำเนินการทางธุรกรรมที่ซับซ้อนด้วยรหัสที่กระชับและอำนวยความสะดวก และรับรองว่ามีการเป็นไปตามลักษณะของการทำธุรกรรมรวมไปถึงความทันทีของข้อมูล
3. เริ่มต้นทำธุรกรรม
3.1 วิธีเริ่มต้นทำธุรกรรมใน ent
ในเฟรมเวิร์ก ent
การทำธุรกรรมใหม่สามารถเริ่มต้นได้ง่ายๆ ภายในขอบเขตที่กำหนดด้วยการใช้เมธอด client.Tx
และคืนค่าอ็อบเจ็กต์การทำธุรกรรม Tx
ตัวอย่างรหัสดังนี้:
tx, err := client.Tx(ctx)
if err != nil {
// จัดการกับข้อผิดพลาดเมื่อเริ่มการทำธุรกรรม
return fmt.Errorf("เกิดข้อผิดพลาดขณะเริ่มการทำธุรกรรม: %w", err)
}
// ดำเนินการต่อไปโดยใช้ tx...
3.2 ดำเนินการภายในการทำธุรกรรม
เมื่ออ็อบเจ็กต์ Tx
ถูกสร้างขึ้นอย่างสำเร็จแล้ว สามารถใช้ในการดำเนินการกับฐานข้อมูลได้ การดำเนินการสร้าง ลบ อัพเดต และคิวรี่ ที่ถูกดำเนินการบนอ็อบเจ็กต์ Tx
จะกลายเป็นส่วนหนึ่งของการทำธุรกรรม ตัวอย่างต่อไปนี้แสดงการดำเนินการชุดของการดำเนินการ:
hub, err := tx.Group.
Create().
SetName("Github").
Save(ctx)
if err != nil {
// หากเกิดข้อผิดพลาด กลับการทำธุรกรรม
return rollback(tx, fmt.Errorf("ล้มเหลวในการสร้างกลุ่ม: %w", err))
}
// การดำเนินการเพิ่มเติมสามารถเพิ่มที่นี่...
// ยืนยันการทำธุรกรรม
tx.Commit()
4. การจัดการข้อผิดพลาดและการกลับคืนในการทำธุรกรรม
4.1 ความสำคัญของการจัดการข้อผิดพลาด
เมื่อทำงานกับฐานข้อมูล ข้อผิดพลาดต่างๆ เช่นปัญหาเครือข่าย การมีข้อขัดแย้งข้อมูล หรือการละเมิดความจำกัดสามารถเกิดขึ้นได้ตลอดเวลา การจัดการข้อผิดพลาดอย่างถูกต้องนั้นสำคัญอย่างมากสำหรับการรักษาความทันทีของข้อมูล ในการทำธุรกรรม หากการดำเนินการล้มเหลว การทำธุรกรรมจำเป็นต้องถูกกลับคืนเพื่อให้แน่ใจว่าการดำเนินการบางส่วนที่เสร็จแล้วซึ่งอาจเกิดความไม่สอดคล้องของฐานข้อมูลไม่ได้รับการทิ้งไว้
4.2 วิธีการในการปฏิบัติการกลับคืน
ในเฟรมเวิร์ก ent
คุณสามารถใช้เมธอด Tx.Rollback()
เพื่อกลับคืนการทำธุรกรรมทั้งหมด โดยทั่วไปจะกำหนดฟังก์ชันผู้ช่วย rollback
เพื่อจัดการกับการกลับคืนและข้อผิดพลาด เช่นตัวอย่างด้านล่าง:
func rollback(tx *ent.Tx, err error) error {
if rerr := tx.Rollback(); rerr != nil {
// หากการกลับคืนล้มเหลว คืนข้อผิดพลาดเดิมและข้อผิดพลาดในการกลับคืนร่วมกัน
err = fmt.Errorf("%w: เกิดข้อผิดพลาดขณะกลับคืนการทำธุรกรรม: %v", err, rerr)
}
return err
}
ด้วยฟังก์ชัน rollback
นี้ เราสามารถจัดการดั้งเดิมและการกลับคืนการทำธุรกรรมเมื่อดำเนินการใดๆ ในการทำธุรกรรมล้มเหลว นี้ ยืนยันว่าแม้ในกรณีของข้อผิดพลาด มันจะไม่มีผลกระทบทางลบ โต้ะต่อความทันทีของฐานข้อมูล ฐานข้อมูลอันถูกต้องสามารถนำกลับไปโดยไม่พบผลกระทบลบสำคัญ
5. การใช้ Transactional Client
ในการใช้งานจริง อาจมีสถานการณ์ที่เราต้องการที่จะแปลงโค้ดที่ไม่ใช่ transactional เป็นโค้ดที่เป็น transactional อย่างรวดเร็ว สำหรับกรณีเช่นนี้ เราสามารถใช้ transactional client เพื่อทำการย้ายโค้ดโดยสะดวก ด้านล่างคือตัวอย่างของวิธีการแปลงโค้ดลูกค้าที่ไม่ใช่ transactional เป็นโค้ดที่รองรับการทำธุรกรรม:
// ในตัวอย่างนี้ เราห่อหุ้มฟังก์ชัน Gen ต้นฉบับไว้ใน transaction
func WrapGen(ctx context.Context, client *ent.Client) error {
// ก่อนอื่น สร้าง transaction
tx, err := client.Tx(ctx)
if err != nil {
return err
}
// ดึง transactional client จาก transaction
txClient := tx.Client()
// ดำเนินการ Gen function โดยใช้ transactional client โดยไม่เปลี่ยนแปลงโค้ด Gen ต้นฉบับ
if err := Gen(ctx, txClient); err != nil {
// หากเกิดข้อผิดพลาด ย้อนกลับ transaction
return rollback(tx, err)
}
// หากสำเร็จ ยืนยัน transaction
return tx.Commit()
}
ในโค้ดข้างต้น transactional client tx.Client()
ถูกใช้เพื่อให้ฟังก์ชัน Gen
ต้นฉบับสามารถทำงานภายใต้การรับรองของการทำธุรกรรมได้ วิธีการนี้ช่วยให้เราสามารถแปลงโค้ดลูกค้าที่ไม่ใช่ transactional เป็นโค้ดที่รองรับการทำธุรกรรมได้อย่างสะดวกโดยมีความผลกระทบต่ำต่อตรรกะต้นฉบับ
6. แนวทางการใช้งานที่ดีสำหรับการทำธุรกรรม
6.1 การจัดการ Transaction ด้วยฟังก์ชัน Callback
เมื่อตรรกะโค้ดของเรากลายเป็นซับซ้อนและเกี่ยวข้องกับการดำเนินการฐานข้อมูลหลายรายการ การบริหารจัดการเหล่านี้ด้วยการทำธุรกรรมที่เป็นกลางกลายมีความสำคัญอย่างยิ่ง ด้านล่างคือตัวอย่างของการจัดการการทำธุรกรรมผ่านฟังก์ชัน callback:
func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
// ใช้ defer และ recover เพื่อจัดการกับสถานการณ์ panic ที่เป็นไปได้
defer func() {
if v := recover(); v != nil {
tx.Rollback()
panic(v)
}
}()
// เรียกใช้ฟังก์ชัน callback ที่ให้มาเพื่อดำเนินการตรรกะธุรกรรมทางธุรกิจ
if err := fn(tx); err != nil {
// ในกรณีที่เกิดข้อผิดพลาด ย้อนกลับ transaction
if rerr := tx.Rollback(); rerr != nil {
err = fmt.Errorf("%w: rolling back transaction: %v", err, rerr)
}
return err
}
// หากธุรกิจทำงานได้โดยไม่เกิดข้อผิดพลาด ยืนยัน transaction
return tx.Commit()
}
โดยใช้ WithTx
ฟังก์ชันครุ่นหน้าธุรกรรมเราสามารถให้ความมั่นใจได้ว่า ถ้าเกิดข้อผิดพลาดหรือข้อยกเว้นภายในธุรกิจ การทำธุรกรรมจะถูกดำเนินที่ได้ถูกต้อง (ทำการยืนยันหรือย้อนกลับ)
6.2 การใช้ Transaction Hooks
คล้ายกับ schema hooks และ runtime hooks เราสามารถลงทะเบียน hooks ภายใน transaction ที่กำลังเปิด (Tx) ซึ่งจะถูกกระตุ้นขึ้นเมื่อ Tx.Commit
หรือ Tx.Rollback
:
func Do(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
tx.OnCommit(func(next ent.Committer) ent.Committer {
return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
// ตรรกะก่อนทำการยืนยันธุรกรรม
err := next.Commit(ctx, tx)
// ตรรกหลังทำการยืนยันธุรกรรม
return err
})
})
tx.OnRollback(func(next ent.Rollbacker) ent.Rollbacker {
return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error {
// ตรรกก่อนย้อนกลับธุรกรรม
err := next.Rollback(ctx, tx)
// ตรรกหลังย้อนกลับธุรกรรม
return err
})
})
// ดำเนินการธุรกิจอื่น ๆ
//
//
//
return err
}
โดยเพิ่ม hooks ในเวลาที่ทำการยืนยันและย้อนกลับ transaction เราสามารถจัดการตรรก์กิจเพิ่มเติม เช่น logging หรือการทำความสะอาดทรัพยากร
7. เข้าใจระดับความกําหนดของธุรกรรมที่แตกต่างกัน
ในระบบฐานข้อมูล การกําหนดระดับความกําหนดของธุรกรรมเป็นสิ่งสําคัญในการป้องกันปัญหาการแข่งขันที่เกิดขึ้น (เช่นการอ่านข้อมูลที่มีการเปลี่ยนแปลงที่ยังไม่ได้รับการยืนยัน ซึ่งอาจทําให้เกิดการอ่านข้อมูลที่ไม่สมบูรณ์ อ่านข้อมูลที่ไม่สามารถทําซ้ําได้ และการอ่านข้อมูลที่แสงร่างขึ้น) นี่คือระดับความกําหนดที่มาตรฐานบางประการ และวิธีการกําหนดระดับความกําหนดของธุรกรรมในโครงสร้าง ent
:
- READ UNCOMMITTED: ระดับต่ําสุด ที่อนุญาตให้อ่านการเปลี่ยนแปลงของข้อมูลที่ยังไม่ได้รับการยืนยัน ซึ่งอาจทําให้เกิดการอ่านข้อมูลที่ไม่สมบูรณ์ อ่านข้อมูลที่ไม่สามารถทําซ้ําได้ และการอ่านข้อมูลที่แสงร่างขึ้น
- READ COMMITTED: อนุญาตให้อ่านและยืนยันข้อมูล ป้องกันการอ่านข้อมูลที่ไม่สมบูรณ์ แต่มีโอกาสที่จะเกิดการอ่านข้อมูลที่ไม่สามารถทําซ้ําได้ และการอ่านข้อมูลที่แสงร่างขึ้น
- REPEATABLE READ: ป้องกันการอ่านข้อมูลเดียวกันหลายครั้งภายในธุรกรรมเดียวกัน ทําให้ได้ผลลัพธ์ที่สม่งคง ป้องกันการอ่านข้อมูลที่ไม่สามารถทําซ้ําได้ แต่อาจทําให้เกิดการอ่านข้อมูลที่แสงร่างขึ้น
- SERIALIZABLE: ระดับความกําหนดที่เข้มงวดที่สุด พยายามป้องกันการอ่านข้อมูลที่ไม่สมบูรณ์ การอ่านข้อมูลที่ไม่สามารถทําซ้ําได้ และการอ่านข้อมูลที่แสงร่างขึ้นโดยล็อคข้อมูลที่เกี่ยวข้อง
ใน ent
, หากไดรเวอร์ฐานข้อมูลรองรั้งรองรับการกําหนดระดับความกําหนดของธุรกรรม จะสามารถกําหนดได้ตามนี้:
// กําหนดระดับความกําหนดของธุรกรรมเป็น repeatable read
tx, err := client.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
การเข้าใจระดับความกําหนดของธุรกรรมและการประยุกต์ใช้งานในฐานข้อมูลเป็นสิ่งสําคัญสําหรับการรักษาความสมบูรณ์ของข้อมูลและความมั่นคงของระบบ นักพัฒนาโปรแกรมควรเลือกระดับความกําหนดที่เหมาะสมโดยพึงพิจารณาจากความต้องการของแอปพลิเคชั่นเฉพาะ đểประสบความสําเร็จในการรักษาความปลอดภัยของข้อมูลและการปรับปรุงประสิทธิภาพของระบบ