首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP MySQL 事務處理完整指南:從 ACID 原理到 mysqli/PDO 實戰

     編輯:PHP 綜合知識

事務與 ACID 特性

資料庫事務 是一組不可分割的操作序列,必須滿足 ACID 四大特性:

特性英文說明轉帳範例
原子性Atomicity全做或全不做,不可部分執行扣款與入帳必須同時成功
一致性Consistency事務前後資料庫狀態一致總金額不變,不會憑空增減
隔離性Isolation並發事務互不干擾轉帳過程中他人查詢見舊餘額
持久性Durability提交後永久保存,抗故障系統崩潰後帳目不丟失

儲存引擎選擇

務必使用 InnoDB。MyISAM 不支援事務,僅適合純讀取/日誌類資料表。

CREATE TABLE accounts (
    id INT PRIMARY KEY,
    balance DECIMAL(12,2) NOT NULL DEFAULT 0
) ENGINE=InnoDB;

mysqli 事務實作

<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'bank_db');
if ($mysqli->connect_errno) {
    die("連線失敗: " . $mysqli->connect_error);
}

$from = 1001; $to = 1002; $amount = 500.00;

try {
    // 1. 關閉自動提交,開啟事務
    $mysqli->autocommit(false);
    // 或: $mysqli->begin_transaction(); // PHP 5.5+ 推薦
    
    // 2. 扣款 (檢查餘額)
    $stmt = $mysqli->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?");
    $stmt->bind_param('dii', $amount, $from, $amount);
    $stmt->execute();
    if ($stmt->affected_rows === 0) {
        throw new Exception("餘額不足或帳戶不存在");
    }
    
    // 3. 入帳
    $stmt = $mysqli->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
    $stmt->bind_param('di', $amount, $to);
    $stmt->execute();
    if ($stmt->affected_rows === 0) {
        throw new Exception("目標帳戶不存在");
    }
    
    // 4. 記錄流水 (可選)
    $stmt = $mysqli->prepare("INSERT INTO transactions (from_id, to_id, amount, created_at) VALUES (?, ?, ?, NOW())");
    $stmt->bind_param('iid', $from, $to, $amount);
    $stmt->execute();
    
    // 5. 全部成功,提交
    $mysqli->commit();
    echo "轉帳成功";
    
} catch (Exception $e) {
    // 任何錯誤,回滾
    $mysqli->rollback();
    echo "轉帳失敗: " . $e->getMessage();
} finally {
    // 恢復自動提交
    $mysqli->autocommit(true);
    $mysqli->close();
}

PDO 事務實作 (推薦,支援多資料庫)

<?php
$pdo = new PDO('mysql:host=localhost;dbname=bank_db;charset=utf8mb4', 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$from = 1001; $to = 1002; $amount = 500.00;

try {
    $pdo->beginTransaction();
    
    // 扣款
    $stmt = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?");
    $stmt->execute([$amount, $from, $amount]);
    if ($stmt->rowCount() === 0) throw new Exception("餘額不足");
    
    // 入帳
    $stmt = $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
    $stmt->execute([$amount, $to]);
    if ($stmt->rowCount() === 0) throw new Exception("目標帳戶不存在");
    
    // 流水
    $stmt = $pdo->prepare("INSERT INTO transactions (from_id, to_id, amount) VALUES (?, ?, ?)");
    $stmt->execute([$from, $to, $amount]);
    
    $pdo->commit();
    echo "轉帳成功";
    
} catch (Exception $e) {
    $pdo->rollBack();
    echo "轉帳失敗: " . $e->getMessage();
}

舊版 mysql_* 函數 (已棄用,僅供維護舊專案參考)

<?php
// 強烈不建議在新專案使用
$conn = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('bank_db');
mysql_query('SET autocommit=0'); // 或 START TRANSACTION

mysql_query("UPDATE accounts SET balance = balance - 500 WHERE id = 1001");
mysql_query("UPDATE accounts SET balance = balance + 500 WHERE id = 1002");

if (mysql_errno()) {
    mysql_query('ROLLBACK');
    echo '失敗';
} else {
    mysql_query('COMMIT');
    echo '成功';
}
mysql_query('SET autocommit=1');

進階主題

隔離等級與鎖

// 設定隔離等級 (需在 beginTransaction 前)
$pdo->exec("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ");

// 常見等級:
// READ UNCOMMITTED - 髒讀、不可重複讀、幻讀
// READ COMMITTED     - 不可重複讀、幻讀 (大多數 DB 預設)
// REPEATABLE READ    - 幻讀 (InnoDB 預設)
// SERIALIZABLE       - 完全串列化,效能最差

儲存點

$pdo->beginTransaction();
$pdo->exec("SAVEPOINT sp1");
// ... 某些操作
if ($error) $pdo->exec("ROLLBACK TO SAVEPOINT sp1"); // 部分回滾
$pdo->commit();

死鎖處理

  • 固定資源存取順序 (如按 ID 排序更新)
  • 縮短事務執行時間,避免長事務
  • 捕獲 `SQLSTATE[HY000]: General error: 1213 Deadlock found` 重試

最佳實踐清單

  1. 只在必要時使用事務:單一查詢無需事務。
  2. 事務越短越好:避免在事務中進行網路請求、複雜運算、sleep。
  3. 預處理語句:防 SQL 注入、提升效能。
  4. 異常處理完整:`try-catch-finally` 確保 rollback/autocommit 恢復。
  5. 測試並發場景:模擬高併發驗證隔離性與死鎖處理。

掌握事務處理是後端開發核心技能,正確使用能保障資料一致性,避免嚴重的財務/業務邏輯錯誤。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved