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

PHP 使用 MySQL 事務 (Transaction) 實戰範例與避坑指南

     編輯:PHP 綜合知識

事務確保多條 SQL「要麼全成功、要麼全失敗」,是資料一致性基石。本文以 PDO 為例 (舊 mysql_* 函式已移除),示範標準事務流程、巢狀事務模擬、死鎖重試、唯讀事務優化,並列出常見誤區對照表。

PDO 標準事務範本

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

try {
    $pdo->beginTransaction();

    // 業務 SQL 1
    $stmt = $pdo->prepare('INSERT INTO `orders` (`user_id`, `amount`, `status`) VALUES (?, ?, 0)');
    $stmt->execute([$userId, $amount]);
    $orderId = $pdo->lastInsertId();

    // 業務 SQL 2 (扣減庫存)
    $stmt = $pdo->prepare('UPDATE `products` SET `stock` = `stock` - 1 WHERE `id` = ? AND `stock` > 0');
    $stmt->execute([$productId]);
    if ($stmt->rowCount() === 0) {
        throw new \Exception('庫存不足');
    }

    // 業務 SQL 3 (記錄流水)
    $stmt = $pdo->prepare('INSERT INTO `order_logs` (`order_id`, `action`) VALUES (?, ?)');
    $stmt->execute([$orderId, 'create']);

    $pdo->commit();
    echo "下單成功,訂單號: $orderId";
} catch (\Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    error_log('下單失敗: ' . $e->getMessage());
    echo "下單失敗: " . $e->getMessage();
}
?>

關鍵規則速查

規則說明
表引擎必須 InnoDBMyISAM 不支援事務,ALTER TABLE `t` ENGINE=InnoDB
自動提交預設開啟PDO 建立連線即 autocommit=1,beginTransaction() 關閉它
DDL 會隱性提交CREATE/ALTER/DROP TABLE、TRUNCATE 會強制 commit,勿放在事務中
事務不可巢狀MySQL 不支援巢狀事務,第二個 begin 會隱性 commit 第一個 (見下方模擬方案)
SELECT ... FOR UPDATE加排他鎖,防止並發修改同一行,需在事務中使用
死鎖自動回滾InnoDB 偵測到死鎖會自動回滾其中一個事務,應用層需捕獲 SQLSTATE[HY000]: General error: 1213 重試

巢狀事務模擬 (Savepoint)

function transactional(callable $callback, PDO $pdo) {
    $savepoint = 'sp_' . uniqid('', true);
    $pdo->exec("SAVEPOINT $savepoint");
    try {
        $result = $callback($pdo);
        $pdo->exec("RELEASE SAVEPOINT $savepoint");
        return $result;
    } catch (\Throwable $e) {
        $pdo->exec("ROLLBACK TO SAVEPOINT $savepoint");
        throw $e;
    }
}

// 使用
$pdo->beginTransaction();
try {
    // 外層邏輯
    transactional(function($pdo) {
        // 內層邏輯,可獨立回滾不影響外層
    }, $pdo);
    $pdo->commit();
} catch (\Throwable $e) {
    $pdo->rollBack();
}
?>

死鎖重試封裝

function withDeadlockRetry(callable $callback, int $maxRetries = 3, int $baseDelayMs = 50) {
    for ($attempt = 0; ; $attempt++) {
        try {
            return $callback();
        } catch (\PDOException $e) {
            if ($e->getCode() === 'HY000' && str_contains($e->getMessage(), '1213')) {
                if ($attempt >= $maxRetries) throw $e;
                usleep($baseDelayMs * 1000 * (1 << $attempt)); // 指數退避
                continue;
            }
            throw $e;
        }
    }
}

// 使用
withDeadlockRetry(function() use ($pdo) {
    $pdo->beginTransaction();
    // ... 業務 ...
    $pdo->commit();
});
?>

唯讀事務優化 (MySQL 8.0+)

$pdo->beginTransaction();
$pdo->exec('SET TRANSACTION READ ONLY'); // 或 START TRANSACTION READ ONLY
// 僅執行 SELECT,無鎖、無 undo log、可從備庫讀取
$pdo->commit();
?>

常見誤區對照

誤區正確做法
用 mysql_query('START TRANSACTION') 字串拼接用 $pdo->beginTransaction(),自動處理驅動層狀態
catch 後忘記 rollBack()永遠在 catch 中檢查 inTransaction() 並回滾
事務中混入 DDL (建表、加索引)DDL 會隱性提交,拆分為遷移腳本與業務事務分開執行
長事務 (超過幾秒)拆分業務、異步處理、或用 Saga 模式補償
以為 SELECT 不需事務需一致性快照時 (如報表、對帳) 必須用事務 + REPEATABLE READ

總結

MySQL 事務核心三步:beginTransaction() → 業務 SQL → commit() / rollBack()。生產環境必備:**InnoDB 引擎、PDO 例外模式、死鎖重試、Savepoint 模擬巢狀、長事務拆分**。掌握這套模式,即可構建可靠的資料一致性保障。

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