資料庫事務確保多步操作的原子性(全成或全敗),是金流轉帳、庫存扣款等關鍵業務的基石。本文基於 PDO + InnoDB,提供標準化事務範本,涵蓋連線配置、自動提交關閉、異常驅動回滾、驗證查詢全流程。
pdo_mysql 已啟用。SELECT, INSERT, UPDATE, DELETE 權限。<?php
// 1. 建立 PDO 連線並配置屬性
$dsn = 'mysql:host=localhost;dbname=your_db;charset=utf8mb4';
$user = 'db_user';
$pass = 'db_pass';
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 錯誤拋出 Exception
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 使用原生預處理
]);
} catch (PDOException $e) {
exit('資料庫連線失敗:' . $e->getMessage());
}
// 2. 事務業務邏輯
try {
// 關閉自動提交,開啟事務
$pdo->beginTransaction();
// 業務 SQL 1:用戶 A 加款
$age = 10;
$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE username = ?");
$stmt1->execute([$age, 'user1']);
if ($stmt1->rowCount() === 0) {
throw new Exception('用戶 user1 不存在或更新失敗');
}
// 業務 SQL 2:用戶 B 扣款(模擬可能失敗)
$stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE username = ? AND balance >= ?");
$stmt2->execute([$age, 'user2', $age]);
if ($stmt2->rowCount() === 0) {
throw new Exception('用戶 user2 餘額不足或不存在');
}
// 所有步驟成功,提交事務
$pdo->commit();
echo '轉帳成功';
} catch (Exception $e) {
// 任何異常觸發回滾
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo '操作失敗:' . $e->getMessage();
}
// 3. 驗證結果
$rows = $pdo->query("SELECT username, balance FROM accounts WHERE username IN ('user1','user2')")->fetchAll();
echo ''; print_r($rows); echo '
';
?>
| 技術點 | 說明 | 最佳實踐 |
|---|---|---|
ATTR_ERRMODE => EXCEPTION | 將 SQL 錯誤轉為 Exception,統一由 try-catch 捕獲 | 永遠啟用,避免靜默失敗 |
beginTransaction() | 關閉自動提交,標記事務起始點 | 業務邏輯前必呼叫 |
prepare() + execute() | 預處理語句防 SQL 注入,支援參數綁定 | 所有動態 SQL 必用 |
rowCount() | 檢查影響列數,判斷業務邏輯是否生效 | 配合業務規則拋出自訂異常 |
inTransaction() | 檢查是否處於事務中,避免重複 rollBack 報錯 | catch 區塊前必檢查 |
MySQL 不支援巢狀事務,但可用 Savepoint 模擬:
<?php
$pdo->beginTransaction();
try {
// 主流程
$pdo->exec("SAVEPOINT sp1");
try {
// 子流程(可獨立回滾)
$pdo->exec("UPDATE ... ");
$pdo->exec("RELEASE SAVEPOINT sp1");
} catch (Exception $e) {
$pdo->exec("ROLLBACK TO SAVEPOINT sp1");
}
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}
?>
fetchAll() 或徹底遍歷結果集。SHOW TABLE STATUS LIKE 'tbl_name' 確認 Engine=InnoDB。innodb_lock_wait_timeout。採用此標準化範本,能將事務邏輯錯誤率降至最低,確保核心業務資料一致性。