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

PHP mysqli 事務控制實戰:銀行轉賬案例與 ACID 原理深度解析

     編輯:PHP 相關知識

事務確保一組操作「要么全成功,要么全失敗」,是金融、庫存、訂單等強一致性場景的基石。本文以經典銀行轉賬為例,講解 mysqli 事務 API、ACID 特性落地、死鎖處理與最佳實踐。

核心代碼:轉賬事務

$mysqli = new mysqli('localhost', 'user', 'pass', 'bank');
$mysqli->set_charset('utf8mb4');

// 1. 關閉自動提交,開啟事務
$mysqli->autocommit(false);
// 等價於:$mysqli->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);

try {
    // 2. 扣款
    $sql1 = "UPDATE accounts SET balance = balance - 200 WHERE id = 1 AND balance >= 200";
    if (!$mysqli->query($sql1) || $mysqli->affected_rows === 0) {
        throw new Exception('Insufficient balance or account not found');
    }

    // 3. 入賬
    $sql2 = "UPDATE accounts SET balance = balance + 200 WHERE id = 2";
    if (!$mysqli->query($sql2)) {
        throw new Exception('Credit failed: ' . $mysqli->error);
    }

    // 4. 提交事務
    $mysqli->commit();
    echo "Transfer succeeded.\n";

} catch (Exception $e) {
    // 5. 回滾事務
    $mysqli->rollback();
    echo "Transfer failed: " . $e->getMessage() . "\n";
} finally {
    $mysqli->autocommit(true); // 恢復自動提交模式
    $mysqli->close();
}

ACID 在 MySQL/InnoDB 中的落地

特性實現機制
Atomicity (原子性)Undo Log 記錄舊值,回滾時反向恢復。
Consistency (一致性)外鍵、唯一索引、CHECK 約束 + 應用層邏輯保證。
Isolation (隔離性)MVCC + 行鎖(Record Lock、Gap Lock、Next-Key Lock),四級隔離級別(READ UNCOMMITTED ~ SERIALIZABLE)。
Durability (持久性)Redo Log (WAL) + Doublewrite Buffer + fsync 策略 (innodb_flush_log_at_trx_commit=1)。

進階:隔離級別與死鎖

// 設置事務隔離級別(需在 begin_transaction 前或 autocommit=false 後)
$mysqli->query("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ");
// 或啟動時指定(mysqli 5.6+)
$mysqli->begin_transaction(MYSQLI_TRANS_START_READ_WRITE | MYSQLI_TRANS_START_WITH_CONSISTENT_SNAPSHOT);
  • 死鎖:InnoDB 自動檢測並回滾其中一事務(返回錯誤碼 1213)。應用層**必須捕獲並重試**(指數退避 + 隨機抖動)。
  • 鎖等待超時:innodb_lock_wait_timeout(默認 50s),超時返回錯誤碼 1205。
// 重試邏輯示例
$maxRetries = 3;
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
    try {
        // ... 事務邏輯 ...
        break; // 成功跳出
    } catch (mysqli_sql_exception $e) {
        if ($e->getCode() === 1213 || $e->getCode() === 1205) {
            if ($attempt === $maxRetries) throw $e;
            usleep(50000 * $attempt + random_int(0, 10000)); // 50ms, 100ms, 150ms + 抖動
            continue;
        }
        throw $e; // 非鎖錯誤直接拋出
    }
}

最佳實踐清單

  1. **盡量縮短事務耗時**:只包含必要寫操作,讀操作(SELECT)移至事務外(除非需一致性快照)。
  2. **按固定順序訪問表/行**:所有事務按相同順序加鎖(如先 id=1 再 id=2),大幅降低死鎖概率。
  3. **使用行級鎖而非表鎖**:InnoDB 默認行鎖,避免 LOCK TABLES。
  4. **異常時必須 rollback()**:防止連接池復用時殘留未提交事務阻塞其他連接。
  5. **監控慢事務**:開啟 long_query_time 並記錄 Trx_id 關聯業務追蹤。

事務不是萬能藥,但用對了就是數據一致性的最後防線。理解 InnoDB 鎖機制、死鎖重試、隔離級別選擇,才能寫出經得起高併發考驗的金融級代碼。

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