資料庫事務是確保資料一致性的核心機制。本文以經典轉賬場景為例,詳細展示如何使用 PHP MySQLi 實現事務的提交與回滾,包含完整程式碼、關鍵步驟解析與生產環境最佳實踐。
事務遵循 ACID 原則:
<?php
header("Content-Type: text/html; charset=utf-8");
// 1. 建立資料庫連線
$mysqli = new mysqli("localhost", "root", "064319", "php");
$mysqli->set_charset("utf8");
if ($mysqli->connect_errno) {
die('資料庫連線失敗: ' . $mysqli->connect_error);
}
// 2. 關閉自動提交,開啟事務
$mysqli->autocommit(false);
$flag = true; // 事務執行狀態標記
try {
// 3. 扣款操作(轉出帳戶)
$query = "UPDATE account SET balance = balance - 1000 WHERE id = 3";
$result = $mysqli->query($query);
if (!$result || $mysqli->affected_rows === 0) {
throw new Exception("扣款失敗:帳戶 3 餘額不足或不存在");
}
// 4. 存款操作(轉入帳戶)
$query = "UPDATE account SET balance = balance + 1000 WHERE id = 2";
$result = $mysqli->query($query);
if (!$result || $mysqli->affected_rows === 0) {
throw new Exception("存款失敗:帳戶 2 不存在");
}
// 5. 所有操作成功,提交事務
$mysqli->commit();
echo '轉賬成功';
} catch (Exception $e) {
// 6. 任一操作失敗,回滾事務
$mysqli->rollback();
echo '轉賬失敗: ' . $e->getMessage();
} finally {
// 7. 恢復自動提交模式並關閉連線
$mysqli->autocommit(true);
$mysqli->close();
}
?>
$mysqli->autocommit(false);
預設 MySQLi 自動提交每條 SQL。關閉後,需手動呼叫 commit() 或 rollback()。
使用 $flag 或 try-catch 追蹤操作狀態。現代 PHP 建議使用異常處理,程式碼更清晰。
if (!$result || $mysqli->affected_rows === 0)
雙重檢查:SQL 執行成功且實際影響資料行。防止 WHERE 條件不匹配導致邏輯錯誤。
commit():永久套用所有變更rollback():撤銷事務內所有變更// 為防止並發修改同一帳戶,建議加鎖查詢
$query = "SELECT balance FROM account WHERE id = 3 FOR UPDATE";
FOR UPDATE 在事務中鎖定該行,防止髒讀與更新衝突。
複雜業務邏輯建議移至存儲過程:
CREATE PROCEDURE transfer_account(
IN from_id INT, IN to_id INT, IN amount DECIMAL(10,2)
)
BEGIN
DECLARE from_balance DECIMAL(10,2);
START TRANSACTION;
SELECT balance INTO from_balance FROM account WHERE id = from_id FOR UPDATE;
IF from_balance < amount THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '餘額不足';
END IF;
UPDATE account SET balance = balance - amount WHERE id = from_id;
UPDATE account SET balance = balance + amount WHERE id = to_id;
COMMIT;
END
function executeWithRetry($mysqli, $callback, $maxRetries = 3) {
for ($i = 0; $i < $maxRetries; $i++) {
try {
return $callback($mysqli);
} catch (mysqli_sql_exception $e) {
if ($e->getCode() === 1213) { // Deadlock
usleep(100000 * ($i + 1)); // 指數退避
continue;
}
throw $e;
}
}
throw new Exception("重試次數耗盡");
}
| 錯誤類型 | 原因 | 解決方案 |
|---|---|---|
| 忘記關閉 autocommit | 單條 SQL 即時生效,無法回滾 | 事務開始前必須 $mysqli->autocommit(false) |
| 未檢查 affected_rows | WHERE 條件不匹配不報錯但邏輯失敗 | 每次寫入操作後檢查 affected_rows > 0 |
| 事務過長 | 鎖持有時間長,降低併發效能 | 事務內僅放核心寫操作,讀操作移至外部 |
| 異常未回滾 | PHP 異常中斷流程,事務懸掛 | 使用 try-catch-finally 確保 rollback/commit |
PHP MySQLi 事務處理透過 autocommit(false)、commit()、rollback() 三大核心方法,配合嚴謹的錯誤檢查與異常處理,能可靠實現資料一致性保障。轉賬場景只是冰山一角,訂單處理、庫存扣減、積分變更等所有涉及多步寫入的業務都應納入事務管理。掌握事務設計模式與進階優化技巧,是構建高可靠系統的關鍵能力。