การจัดการ Transaction ด้วย PDO
ในบทเรียนก่อนหน้านี้ เราได้เรียนรู้เกี่ยวกับการสร้าง, อ่าน, แก้ไข, และลบข้อมูล (CRUD Operations) โดยใช้ PDO (PHP Data Objects) ในบทนี้ เราจะมาเรียนรู้เกี่ยวกับการจัดการ Transaction ซึ่งเป็นเครื่องมือสำคัญในการรักษาความถูกต้องของข้อมูลเมื่อมีการดำเนินการหลายอย่างที่เกี่ยวข้องกันในฐานข้อมูล
Transaction คืออะไร?
Transaction คือลำดับของการดำเนินการ (operations) ที่ถือว่าเป็นหน่วยการทำงานเดียว (atomic unit) ซึ่งหมายความว่าการดำเนินการทั้งหมดใน Transaction จะต้องสำเร็จ (commit) หรือล้มเหลวทั้งหมด (rollback) หากมีส่วนใดส่วนหนึ่งของ Transaction ล้มเหลว การเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นก่อนหน้านั้นจะถูกยกเลิก เพื่อให้ฐานข้อมูลกลับสู่สถานะเดิมก่อนที่จะเริ่ม Transaction
ลองนึกภาพว่าคุณกำลังโอนเงินจากบัญชีหนึ่งไปยังอีกบัญชีหนึ่ง การโอนเงินนี้ประกอบด้วยสองขั้นตอน:
- ลดจำนวนเงินในบัญชีต้นทาง
- เพิ่มจำนวนเงินในบัญชีปลายทาง
หากขั้นตอนแรกล้มเหลว (เช่น บัญชีต้นทางมีเงินไม่พอ) แต่ขั้นตอนที่สองสำเร็จ เงินก็จะหายไปจากระบบ! Transaction ช่วยป้องกันปัญหานี้ได้ โดยถ้าขั้นตอนแรกล้มเหลว ขั้นตอนที่สองจะไม่ถูกดำเนินการ และบัญชีทั้งสองจะอยู่ในสถานะเดิม
ทำไมต้องใช้ Transaction?
Transaction มีประโยชน์อย่างมากในการรักษาความสอดคล้องของข้อมูล (data consistency) ในสถานการณ์ที่ซับซ้อน ซึ่งเกี่ยวข้องกับการดำเนินการหลายอย่างที่ต้องเกิดขึ้นพร้อมกันหรือไม่มีเลย ตัวอย่างเช่น:
- การโอนเงินระหว่างบัญชี (ดังที่กล่าวมา)
- การสั่งซื้อสินค้าออนไลน์ (ต้องบันทึกข้อมูลการสั่งซื้อ, ลดจำนวนสินค้าคงคลัง, และบันทึกการชำระเงิน)
- การลงทะเบียนผู้ใช้ (ต้องบันทึกข้อมูลผู้ใช้, สร้างบัญชี, และส่งอีเมลยืนยัน)
หากไม่มี Transaction และเกิดข้อผิดพลาดระหว่างการดำเนินการ อาจทำให้ข้อมูลในฐานข้อมูลไม่ถูกต้องหรือไม่สมบูรณ์
การเริ่มต้น Transaction
ในการเริ่มต้น Transaction ด้วย PDO เราจะใช้เมธอด beginTransaction() ของอ็อบเจ็กต์ PDO:
try {
// สร้างการเชื่อมต่อ PDO (สมมติว่า $pdo ถูกสร้างไว้แล้ว)
// เริ่มต้น Transaction
$pdo->beginTransaction();
// ดำเนินการ SQL queries ที่เกี่ยวข้องกับ Transaction
// ...
} catch (PDOException $e) {
// เกิดข้อผิดพลาด
// ...
}
โค้ดนี้จะเริ่มต้น Transaction บนการเชื่อมต่อฐานข้อมูลที่ระบุ เมื่อ Transaction เริ่มต้นแล้ว การเปลี่ยนแปลงใดๆ ที่เกิดขึ้นกับฐานข้อมูลจะไม่ถูกบันทึกถาวร จนกว่าเราจะ commit Transaction
การ Commit Transaction
หากการดำเนินการทั้งหมดใน Transaction สำเร็จ เราจะบันทึกการเปลี่ยนแปลงทั้งหมดลงในฐานข้อมูลอย่างถาวร โดยใช้เมธอด commit():
try {
// สร้างการเชื่อมต่อ PDO (สมมติว่า $pdo ถูกสร้างไว้แล้ว)
// เริ่มต้น Transaction
$pdo->beginTransaction();
// ดำเนินการ SQL queries ที่เกี่ยวข้องกับ Transaction
$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - :amount WHERE account_id = :account_from");
$stmt1->bindParam(':amount', $amount, PDO::PARAM_INT);
$stmt1->bindParam(':account_from', $accountFrom, PDO::PARAM_INT);
$stmt1->execute();
$stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance + :amount WHERE account_id = :account_to");
$stmt2->bindParam(':amount', $amount, PDO::PARAM_INT);
$stmt2->bindParam(':account_to', $accountTo, PDO::PARAM_INT);
$stmt2->execute();
// Commit Transaction
$pdo->commit();
echo "Transaction สำเร็จ!";
} catch (PDOException $e) {
// เกิดข้อผิดพลาด
// ...
}
หาก commit() สำเร็จ การเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นระหว่าง beginTransaction() และ commit() จะถูกบันทึกถาวรในฐานข้อมูล
การ Rollback Transaction
หากมีการดำเนินการใดๆ ใน Transaction ล้มเหลว เราจะยกเลิกการเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นก่อนหน้านี้ โดยใช้เมธอด rollback():
try {
// สร้างการเชื่อมต่อ PDO (สมมติว่า $pdo ถูกสร้างไว้แล้ว)
// เริ่มต้น Transaction
$pdo->beginTransaction();
// ดำเนินการ SQL queries ที่เกี่ยวข้องกับ Transaction
$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - :amount WHERE account_id = :account_from");
$stmt1->bindParam(':amount', $amount, PDO::PARAM_INT);
$stmt1->bindParam(':account_from', $accountFrom, PDO::PARAM_INT);
$stmt1->execute();
//จำลองการเกิดข้อผิดพลาด
if($amount > 10000){
throw new Exception("จำนวนเงินมากเกินไป");
}
$stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance + :amount WHERE account_id = :account_to");
$stmt2->bindParam(':amount', $amount, PDO::PARAM_INT);
$stmt2->bindParam(':account_to', $accountTo, PDO::PARAM_INT);
$stmt2->execute();
// Commit Transaction
$pdo->commit();
echo "Transaction สำเร็จ!";
} catch (PDOException | Exception $e) {
// เกิดข้อผิดพลาด
echo "Transaction ล้มเหลว: " . $e->getMessage();
// Rollback Transaction
$pdo->rollBack();
}
หาก rollback() ถูกเรียกใช้ การเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นระหว่าง beginTransaction() และ rollback() จะถูกยกเลิก และฐานข้อมูลจะกลับสู่สถานะเดิมก่อนที่จะเริ่ม Transaction
ตัวอย่างการใช้งาน Transaction
ต่อไปนี้เป็นตัวอย่างที่สมบูรณ์ของการใช้ Transaction ในการโอนเงินระหว่างบัญชี:
<?php
// ข้อมูลการเชื่อมต่อฐานข้อมูล
$host = 'localhost';
$db = 'testdb';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
$accountFrom = 1; // รหัสบัญชีต้นทาง
$accountTo = 2; // รหัสบัญชีปลายทาง
$amount = 500; // จำนวนเงินที่ต้องการโอน
// เริ่มต้น Transaction
$pdo->beginTransaction();
// ลดจำนวนเงินในบัญชีต้นทาง
$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - :amount WHERE account_id = :account_from");
$stmt1->bindParam(':amount', $amount, PDO::PARAM_INT);
$stmt1->bindParam(':account_from', $accountFrom, PDO::PARAM_INT);
$stmt1->execute();
// เพิ่มจำนวนเงินในบัญชีปลายทาง
$stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance + :amount WHERE account_id = :account_to");
$stmt2->bindParam(':amount', $amount, PDO::PARAM_INT);
$stmt2->bindParam(':account_to', $accountTo, PDO::PARAM_INT);
$stmt2->execute();
// Commit Transaction
$pdo->commit();
echo "Transaction สำเร็จ! โอนเงิน $amount จากบัญชี $accountFrom ไปยังบัญชี $accountTo";
} catch (PDOException $e) {
// เกิดข้อผิดพลาด
echo "Transaction ล้มเหลว: " . $e->getMessage();
// Rollback Transaction
$pdo->rollBack();
}
?>
ในตัวอย่างนี้ หากการลดจำนวนเงินในบัญชีต้นทางสำเร็จ แต่การเพิ่มจำนวนเงินในบัญชีปลายทางล้มเหลว (เช่น บัญชีปลายทางไม่มีอยู่) Transaction จะถูก rollback และการเปลี่ยนแปลงทั้งหมดจะถูกยกเลิก
สรุป
Transaction เป็นเครื่องมือที่มีประสิทธิภาพในการรักษาความถูกต้องของข้อมูลในฐานข้อมูล การใช้ Transaction ช่วยให้เราสามารถดำเนินการหลายอย่างที่เกี่ยวข้องกันได้อย่างปลอดภัย และมั่นใจได้ว่าข้อมูลในฐานข้อมูลจะสอดคล้องกันเสมอ ในบทเรียนถัดไป เราจะเรียนรู้เกี่ยวกับการตรวจสอบ (validation) ข้อมูลที่รับมาจากผู้ใช้ ก่อนที่จะนำไปบันทึกในฐานข้อมูล เพื่อป้องกันปัญหาต่างๆ ที่อาจเกิดขึ้น