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

Yii2 事務管理深度解析:從基礎用法到分散業務邏輯的統一回滾

     編輯:PHP 綜合知識

為什麼需要事務?

業務邏輯往往涉及多張表的寫入操作(如:用戶註冊需同時寫入 user、profile、balance 表)。若中間某步失敗,已寫入的數據必須回滾,保證數據一致性。Yii2 基於 PDO 封裝了 yii\db\Transaction,支援嵌套事務(保存點)與多模型協同。

核心 API 速查

方法說明
Yii::$app->db->beginTransaction()開啟事務,返回 Transaction 實例
$tr->commit()提交事務
$tr->rollBack()回滾事務
$tr->getIsActive()判斷事務是否處於活動狀態

場景一:捕獲異常自動回滾

public function actionTestException() {
    $tr = Yii::$app->db->beginTransaction();
    try {
        for ($i=1; $i<=3; $i++) {
            $area = new Areas();
            $area->name = 'name'.$i;
            $area->sort = 1;
            $area->save();
        }
        // 故意觸發異常:字段名拼寫錯誤
        $area = new Areas();
        $area->name = 'ab'.$i;
        $area->sorta = 1; // 不存在的屬性
        $area->save();    // 拋出 Exception
        $tr->commit();
    } catch (\Exception $e) {
        $tr->rollBack();
        echo 'rollback: ' . $e->getMessage();
    }
}
// 結果:三條數據均未入庫,事務完美回滾

場景二:業務邏輯失敗(無異常)需手動拋出

ActiveRecord::save() 失敗時返回 false 而非拋出異常,必須手動 throw new \yii\db\Exception() 觸發回滾。

public function actionTestLogicFail() {
    $tr = Yii::$app->db->beginTransaction();
    try {
        for ($i=1; $i<=3; $i++) {
            $area = new Areas();
            $area->name = 'name'.$i;
            $area->sort = 1;
            if (!$area->save()) throw new \yii\db\Exception('保存失敗');
        }
        $area = new Areas();
        $area->name = null; // 假設 name 非空約束
        $area->sort = 1;
        if (!$area->save()) throw new \yii\db\Exception('關鍵業務失敗');
        $tr->commit();
    } catch (\Exception $e) {
        $tr->rollBack();
        echo 'rollback: ' . $e->getMessage();
    }
}
// 結果:前三條也被回滾,保證原子性

場景三:跨 Model/Service 的分散式業務統一事務

真實項目中,寫入邏輯分散在各 Model 靜態方法中,Controller 只負責編排。關鍵在於全庫共用同一 DB 連接(單例),Yii2 Connection::beginTransaction() 內部緩存了 Transaction 實例,同一請求周期內多次調用返回同一對象,實現天然的「隱式傳遞」。

// Controller
public function actionReg() {
    $tr = Yii::$app->db->beginTransaction();
    try {
        $uid = App::getSeNo();                    // 發號器(寫 sequence 表)
        UserProfile::add($uid, $name, $gender, $sign); // 寫用戶資料
        UserBalance::initUserBalance($uid);       // 初始化余額
        $tr->commit();
        return ['success'=>true, 'balance'=>UserBalance::findOne(['user_id'=>$uid])];
    } catch (\Exception $e) {
        $tr->rollBack();
        return ['success'=>false, 'msg'=>$e->getMessage()];
    }
}

// UserProfile::add()
public static function add($uid, $name, $gender, $sign) {
    $model = new self();
    $model->user_id = $uid;
    $model->nikename = $name;
    $model->gender = $gender;
    $model->intro = $sign;
    if (!$model->insert()) throw new \yii\db\Exception('用戶資料寫入失敗');
    return $model;
}

// UserBalance::initUserBalance()
public static function initUserBalance($uid) {
    $model = new self();
    $model->user_id = $uid;
    $model->price = '0';
    if (!$model->insert()) throw new \yii\db\Exception('余額初始化失敗');
    return $model;
}

原理深挖:為什麼跨 Model 也能生效?

// yii\db\Connection 簡化源碼
private $_transaction;

public function getTransaction() {
    return $this->_transaction && $this->_transaction->getIsActive() 
        ? $this->_transaction : null;
}

public function beginTransaction($isolationLevel = null) {
    $this->open();
    if (($transaction = $this->getTransaction()) === null) {
        $transaction = $this->_transaction = new Transaction(['db'=>$this]);
    }
    $transaction->begin($isolationLevel);
    return $transaction;
}

同一請求內,Yii::$app->db 是單例,_transaction 緩存確保所有 beginTransaction() 返回同一 Transaction 實例,_level 計數器實現嵌套(保存點)支持。

最佳實踐清單

  1. 數據庫引擎必須為 InnoDB(MyISAM 不支持事務)。
  2. 盡量在 Service 層 開啟事務,Controller 僅做參數校驗與響應。
  3. 所有 save()/insert()/update() 失敗必須 throw new \yii\db\Exception()。
  4. 異常捕獲使用 catch (\Throwable $e)(PHP 7+)兼容 Error 與 Exception。
  5. 長事務避免在事務中調用外部 API、發送郵件、睡眠等耗時操作,防止鎖競爭。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved