首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

PHP MySQLi 事務處理實戰:自動提交控制、保存點、異常回滾與最佳實踐模式

     編輯:MySQL 資料庫相關知識

事務核心 API 對照

SQL 語句MySQLi 面向對象MySQLi 過程式PDO
START TRANSACTION$mysqli->begin_transaction()mysqli_begin_transaction($link)$pdo->beginTransaction()
COMMIT$mysqli->commit()mysqli_commit($link)$pdo->commit()
ROLLBACK$mysqli->rollback()mysqli_rollback($link)$pdo->rollBack()
SET autocommit=0$mysqli->autocommit(false)mysqli_autocommit($link, false)$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, false)
保存點$mysqli->savepoint('sp1')mysqli_savepoint($link, 'sp1')不支持 (需 SQL)
回滾到保存點$mysqli->rollback('sp1')mysqli_rollback($link, 'sp1')不支持
釋放保存點$mysqli->release_savepoint('sp1')mysqli_release_savepoint($link, 'sp1')不支持

標準事務模板 (面向對象、異常安全)


autocommit(false);
    
    try {
        // 2. 業務邏輯:扣款
        $stmt = $mysqli->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?");
        $stmt->bind_param('dii', $amount, $fromAccount, $amount); // 余額檢查在 SQL 層
        $stmt->execute();
        if ($stmt->affected_rows === 0) {
            throw new \RuntimeException("Insufficient balance or account not found: $fromAccount");
        }
        $stmt->close();

        // 3. 可選保存點:入賬前
        $mysqli->savepoint('before_credit');

        // 4. 業務邏輯:入賬
        $stmt = $mysqli->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
        $stmt->bind_param('di', $amount, $toAccount);
        $stmt->execute();
        if ($stmt->affected_rows === 0) {
            throw new \RuntimeException("Target account not found: $toAccount");
        }
        $stmt->close();

        // 5. 提交事務
        $mysqli->commit();
        return true;
    } catch (\Throwable $e) {
        // 6. 異常回滾 (自動釋放鎖、撤銷變更)
        $mysqli->rollback();
        // 記錄日誌、拋出業務異常
        error_log("Transfer failed: " . $e->getMessage());
        throw new \RuntimeException("Transfer failed: " . $e->getMessage(), 0, $e);
    } finally {
        // 7. 恢復自動提交 (歸還連接池時必須)
        $mysqli->autocommit(true);
    }
}

進階模式:嵌套事務模擬 (保存點)

MySQL 不支持真正嵌套事務,但可用保存點模擬子事務:


function complexOrderProcess(\mysqli $mysqli, array $items): void
{
    $mysqli->autocommit(false);
    try {
        $orderId = createOrderHeader($mysqli); // 內部自行 savepoint/commit/rollback
        
        foreach ($items as $item) {
            $sp = "item_{$item['id']}";
            $mysqli->savepoint($sp);
            try {
                createOrderLine($mysqli, $orderId, $item);
                deductInventory($mysqli, $item);
                $mysqli->release_savepoint($sp); // 子操作成功
            } catch (\Throwable $e) {
                $mysqli->rollback($sp); // 僅回滾當前商品操作
                // 記錄失敗商品,繼續處理下一個
                logFailedItem($item, $e);
            }
        }
        
        $mysqli->commit(); // 整單提交
    } catch (\Throwable $e) {
        $mysqli->rollback();
        throw $e;
    } finally {
        $mysqli->autocommit(true);
    }
}

只讀事務優化 (MySQL 5.6+)


$mysqli->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
// 或
$mysqli->begin_transaction(MYSQLI_TRANS_START_READ_WRITE); // 顯式讀寫
// 好處:只讀事務不分配回滾段、不加鎖、可在從庫執行、優化器可選擇更快計劃

一致性快照讀 (REPEATABLE READ + 事務)


$mysqli->autocommit(false);
$mysqli->query("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"); // 默認級別
// 事務內所有 SELECT 見到同一快照 (事務啟動時刻)
// 適用:報表導出、對賬、批量計算需一致性視圖
// 注意:長事務持有快照導致 Undo Log 膨脹、History List Length 增長,影響 Purge

死鎖自動重試裝飾器


function withDeadlockRetry(callable $callback, int $maxRetries = 3, int $baseDelayMs = 50): mixed
{
    for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
        try {
            return $callback();
        } catch (\mysqli_sql_exception $e) {
            if ($e->getCode() === 1213 || $e->getCode() === 1205) { // ER_LOCK_DEADLOCK / ER_LOCK_WAIT_TIMEOUT
                $delay = $baseDelayMs * (2 ** $attempt) + random_int(0, 50); // 指數退避 + 抖動
                usleep($delay * 1000);
                continue;
            }
            throw $e;
        }
    }
    throw new \RuntimeException("Deadlock retry exhausted after $maxRetries attempts");
}

// 使用
withDeadlockRetry(function() use ($mysqli, $from, $to, $amt) {
    return transferFunds($mysqli, $from, $to, $amt);
});

連接池與事務邊界關鍵規則

  1. 連接歸還池前必須 autocommit(true),否則下一個借用者繼承「事務中」狀態 → 數據不一致、鎖泄漏。
  2. 不要在事務中執行耗時操作 (HTTP 請求、睡眠、大計算),將鎖持有時間降到毫秒級。
  3. 異常時必須 rollback(),即使 autocommit(false) 下連接斷開會隱式回滾,顯式調用利於日誌審計。
  4. 只讀業務強制 READ ONLY 事務,利於從庫分流、減少主庫壓力。

避坑清單

  • ❌ 混用 mysqli_autocommit() 和 START TRANSACTION SQL → 狀態不同步。
  • ❌ finally 塊中漏寫 autocommit(true) → 連接池污染。
  • ❌ 事務中調用其他服務 (RPC/HTTP) → 分布式事務陷阱,改用 Saga/Outbox 模式。
  • ❌ 忽略死鎖重試 → 高併發下必現業務失敗。
  • ✅ 統一封裝 TransactionalExecutor 服務,業務代碼只寫 callback,事務邊界/重試/日誌/監控全自動化。

事務是數據一致性的基石,**MySQLi 提供了完整的面向對象事務 API**,配合 保存點模擬嵌套、只讀事務優化、死鎖指數退避重試、連接池邊界管理,能構建出健壯、高性能、可觀測的數據訪問層。建議將事務控制封裝為基礎設施代碼,業務邏輯零感知。

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