你想了解事务的三个隔离级别(读已提交、可重复读、串行化)如何开启,以及在 PHP 的 YII2 框架中具体怎么使用,我会先讲清楚隔离级别的基础操作,再聚焦 YII2 给出可直接落地的示例。

先明确前提

  1. 我们常说的三个核心隔离级别(READ COMMITTEDREPEATABLE READSERIALIZABLE),数据库层面默认已支持(MySQL InnoDB 默认为 REPEATABLE READ,PostgreSQL 默认为 READ COMMITTED)。
  2. 隔离级别可以两种方式设置:全局生效(对所有连接/事务有效)、会话级生效(仅对当前数据库连接/事务有效,推荐在业务中使用这种,避免影响其他操作)。
  3. YII2 中无需直接写原生 SQL 切换隔离级别,框架提供了封装好的 API,更简洁且兼容不同数据库。

第一步:先了解原生 SQL 开启方式(理解底层)

先看原生 SQL,帮助你理解 YII2 封装的本质,适用于所有 PHP 场景(不限于 YII2)。

1. 查看当前隔离级别

-- MySQL
SELECT @@transaction_isolation;

-- PostgreSQL
SELECT current_setting('transaction_isolation');

2. 全局设置(不推荐,影响整个数据库实例)

-- MySQL:设置全局隔离级别为 READ COMMITTED
SET GLOBAL transaction_isolation = 'READ-COMMITTED';

-- MySQL:设置全局隔离级别为 REPEATABLE READ(默认)
SET GLOBAL transaction_isolation = 'REPEATABLE-READ';

-- MySQL:设置全局隔离级别为 SERIALIZABLE
SET GLOBAL transaction_isolation = 'SERIALIZABLE';

-- 注意:全局设置需要重新建立数据库连接才能生效,且会影响所有后续连接,仅用于数据库配置初始化。并且数据库重启后会重置(读取配置文件,如需重启也生效需要修改配置文件)

3. 会话级设置(推荐,仅当前连接有效)

-- MySQL:设置当前会话隔离级别为 READ COMMITTED
SET SESSION transaction_isolation = 'READ-COMMITTED';

-- 会话级设置后,后续当前连接的所有事务都会使用该隔离级别,直到连接断开或重新设置

4. 事务内设置(更灵活,仅当前事务有效)

-- 开启事务前/后设置(不同数据库语法略有差异,MySQL 推荐事务内设置)
START TRANSACTION;
-- 设置当前事务隔离级别为 SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 执行查询/更新操作
SELECT stock FROM goods WHERE id = 1;
UPDATE goods SET stock = stock - 1 WHERE id = 1;
COMMIT;

第二步:YII2 框架中的具体使用(核心重点)

YII2 对数据库事务和隔离级别做了良好封装,主要通过 yii\db\Connectionyii\db\Transaction 类实现,推荐两种使用方式:场景化选择即可。

前置准备:确认 YII2 数据库配置

先确保你的 config/db.php(或 config/web.php/config/console.php)已正确配置数据库连接,示例:

<?php
return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=localhost;dbname=your_db;charset=utf8mb4',
    'username' => 'root',
    'password' => 'your_password',
    'charset' => 'utf8mb4',
    // 可选:全局设置事务隔离级别(不推荐,优先会话/事务级)
    // 'transactionIsolation' => \yii\db\Transaction::ISOLATION_READ_COMMITTED,
];

核心:YII2 隔离级别常量(无需记原生字符串,直接用框架常量)

YII2 定义了事务隔离级别的常量,兼容不同数据库,推荐直接使用:

YII2 常量对应隔离级别说明
\yii\db\Transaction::ISOLATION_READ_COMMITTED读已提交大多数业务场景首选,兼顾性能和一致性
\yii\db\Transaction::ISOLATION_REPEATABLE_READ可重复读MySQL InnoDB 默认,适合需要保证同一事务内查询结果稳定的场景
\yii\db\Transaction::ISOLATION_SERIALIZABLE串行化最高级别,仅适用于低并发、强一致性场景(如财务)

方式 1:手动控制事务(灵活,推荐复杂业务)

手动开启、设置隔离级别、提交/回滚,适合需要精细控制事务流程的场景(比如查询后根据条件判断是否继续执行更新)。

完整示例(扣减商品库存,使用「可重复读」隔离级别)

<?php
namespace app\controllers;

use Yii;
use yii\web\Controller;
use yii\db\Transaction; // 引入事务类

class GoodsController extends Controller
{
    // 扣减库存接口
    public function actionDeductStock($goodsId = 1)
    {
        // 1. 获取数据库连接实例
        $db = Yii::$app->db;
        
        // 2. 定义事务变量(初始为 null)
        $transaction = null;
        
        try {
            // 3. 开启事务并设置隔离级别(核心步骤)
            // 第二个参数传入隔离级别常量,指定当前事务使用的隔离级别
            $transaction = $db->beginTransaction(Transaction::ISOLATION_REPEATABLE_READ);
            
            // 可选:如果需要中途修改隔离级别(极少场景),可通过 transaction 对象设置
            // $transaction->setIsolationLevel(Transaction::ISOLATION_SERIALIZABLE);
            
            // 4. 执行查询操作(事务内)
            $currentStock = $db->createCommand(
                'SELECT stock FROM goods WHERE id = :goodsId',
                [':goodsId' => $goodsId]
            )->queryScalar();
            
            // 校验库存
            if ($currentStock === false || $currentStock <= 0) {
                throw new \Exception("库存不足,无法扣减");
            }
            
            // 5. 执行更新操作(事务内)
            $affectedRows = $db->createCommand(
                'UPDATE goods SET stock = stock - 1 WHERE id = :goodsId',
                [':goodsId' => $goodsId]
            )->execute();
            
            if ($affectedRows <= 0) {
                throw new \Exception("库存扣减失败,未修改任何数据");
            }
            
            // 6. 事务提交(所有操作执行成功,提交事务)
            $transaction->commit();
            
            return $this->asJson([
                'code' => 200,
                'msg' => '库存扣减成功',
                'data' => ['new_stock' => $currentStock - 1]
            ]);
            
        } catch (\Exception $e) {
            // 7. 事务回滚(出现异常,回滚所有操作,恢复数据)
            if ($transaction !== null && $transaction->isActive) {
                $transaction->rollBack();
            }
            
            return $this->asJson([
                'code' => 500,
                'msg' => '操作失败:' . $e->getMessage(),
                'data' => []
            ]);
        }
    }
}

方式 2:使用 transaction() 方法(简洁,推荐简单业务)

YII2 提供了 db->transaction() 快捷方法,自动处理「开启事务-执行逻辑-提交/回滚」,无需手动调用 commit()rollBack(),更简洁。

完整示例(使用「读已提交」隔离级别,更新用户信息)

<?php
namespace app\controllers;

use Yii;
use yii\web\Controller;
use yii\db\Transaction;

class UserController extends Controller
{
    // 更新用户昵称和手机号
    public function actionUpdateUser($userId = 1)
    {
        $db = Yii::$app->db;
        
        // 1. 调用 transaction() 方法,传入隔离级别和回调函数
        $result = $db->transaction(function () use ($db, $userId) {
            // 回调函数内的所有数据库操作,都在该事务内执行
            // 步骤1:查询用户是否存在(事务内)
            $user = $db->createCommand(
                'SELECT * FROM user WHERE id = :userId',
                [':userId' => $userId]
            )->queryOne();
            
            if (!$user) {
                throw new \Exception("用户不存在");
            }
            
            // 步骤2:更新用户昵称和手机号
            $db->createCommand()
                ->update(
                    'user',
                    [
                        'nickname' => '新昵称_' . uniqid(),
                        'phone' => '138xxxx' . rand(1000, 9999)
                    ],
                    ['id' => $userId]
                )->execute();
            
            // 步骤3:返回处理结果(回调函数返回值会被赋值给 $result)
            return [
                'code' => 200,
                'msg' => '用户更新成功',
                'data' => ['user_id' => $userId]
            ];
        }, Transaction::ISOLATION_READ_COMMITTED); // 第二个参数:指定隔离级别
        
        // 2. 返回结果
        return $this->asJson($result);
    }
}

说明:

  • 回调函数内如果抛出异常,框架会自动回滚事务;如果正常执行完毕,框架会自动提交事务。
  • 这种方式代码更简洁,减少手动操作的遗漏(比如忘记回滚),适合逻辑简单的事务场景。

方式 3:YII2 ActiveRecord 中使用(结合模型,更符合 ORM 开发习惯)

如果你的项目使用 YII2 的 ActiveRecord 模型(比如 Goods.phpUser.php),也可以结合模型使用事务,隔离级别设置方式和前面一致。

示例(Goods 模型 + 事务 + 串行化隔离级别)

  1. 先定义 Goods 模型(app/models/Goods.php):

    <?php
    namespace app\models;
    
    use yii\db\ActiveRecord;
    
    class Goods extends ActiveRecord
    {
    public static function tableName()
    {
        return 'goods'; // 对应数据库表名
    }
    }
  2. 控制器中使用模型 + 事务:

    <?php
    namespace app\controllers;
    
    use Yii;
    use yii\web\Controller;
    use yii\db\Transaction;
    use app\models\Goods;
    
    class GoodsModelController extends Controller
    {
    public function actionDeductStockModel($goodsId = 1)
    {
        $db = Yii::$app->db;
        
        try {
            // 开启事务,使用串行化隔离级别(仅低并发强一致性场景使用)
            $transaction = $db->beginTransaction(Transaction::ISOLATION_SERIALIZABLE);
            
            // 1. 模型查询(事务内)
            $goods = Goods::findOne($goodsId);
            if (!$goods || $goods->stock <= 0) {
                throw new \Exception("库存不足");
            }
            
            // 2. 模型更新(事务内)
            $goods->stock -= 1;
            if (!$goods->save(false)) { // false:跳过模型验证(如需验证可移除)
                throw new \Exception("库存更新失败");
            }
            
            // 提交事务
            $transaction->commit();
            
            return $this->asJson([
                'code' => 200,
                'msg' => '库存扣减成功',
                'data' => ['new_stock' => $goods->stock]
            ]);
            
        } catch (\Exception $e) {
            if ($transaction !== null && $transaction->isActive) {
                $transaction->rollBack();
            }
            
            return $this->asJson([
                'code' => 500,
                'msg' => '操作失败:' . $e->getMessage(),
                'data' => []
            ]);
        }
    }
    }

注意事项(YII2 中使用事务隔离级别必看)

  1. 隔离级别生效前提:必须在「开启事务前/时」设置,事务开启后修改隔离级别可能无效(不同数据库支持度不同)。
  2. 避免长时间事务:尤其是高隔离级别(如串行化),事务执行时间越长,锁占用时间越久,容易导致并发阻塞,尽量只包含核心的查询和更新操作。
  3. 异常处理:必须捕获所有可能的异常(\Exception\Throwable),否则事务可能无法正常回滚,导致数据不一致。
  4. 数据库兼容性SERIALIZABLE 级别在所有数据库中都支持,但部分轻量数据库(如 SQLite)对隔离级别的支持有限,需提前测试。
  5. 不要嵌套事务:YII2 中的嵌套事务是「模拟事务」(基于保存点),隔离级别设置仅对最外层事务有效,尽量避免嵌套事务设计。

总结

  1. YII2 中推荐使用框架封装的隔离级别常量(Transaction::ISOLATION_xxx),无需写原生 SQL,兼容性更好。
  2. 事务使用分两种场景:复杂业务用「手动控制事务」(灵活),简单业务用 db->transaction() 快捷方法(简洁)。
  3. 隔离级别选择:大多数业务用「读已提交」,需要稳定查询结果用「可重复读」,强一致性低并发场景用「串行化」。
  4. 核心要点:事务内必须包含完整的查询-更新逻辑,异常时务必回滚,避免长时间占用事务锁。

标签: none

添加新评论