如何使用mysql的三种事务隔离级别控制事务行为及yii2方法
你想了解事务的三个隔离级别(读已提交、可重复读、串行化)如何开启,以及在 PHP 的 YII2 框架中具体怎么使用,我会先讲清楚隔离级别的基础操作,再聚焦 YII2 给出可直接落地的示例。
先明确前提
- 我们常说的三个核心隔离级别(
READ COMMITTED、REPEATABLE READ、SERIALIZABLE),数据库层面默认已支持(MySQL InnoDB 默认为REPEATABLE READ,PostgreSQL 默认为READ COMMITTED)。 - 隔离级别可以两种方式设置:全局生效(对所有连接/事务有效)、会话级生效(仅对当前数据库连接/事务有效,推荐在业务中使用这种,避免影响其他操作)。
- 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\Connection 和 yii\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.php、User.php),也可以结合模型使用事务,隔离级别设置方式和前面一致。
示例(Goods 模型 + 事务 + 串行化隔离级别)
先定义 Goods 模型(
app/models/Goods.php):<?php namespace app\models; use yii\db\ActiveRecord; class Goods extends ActiveRecord { public static function tableName() { return 'goods'; // 对应数据库表名 } }控制器中使用模型 + 事务:
<?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 中使用事务隔离级别必看)
- 隔离级别生效前提:必须在「开启事务前/时」设置,事务开启后修改隔离级别可能无效(不同数据库支持度不同)。
- 避免长时间事务:尤其是高隔离级别(如串行化),事务执行时间越长,锁占用时间越久,容易导致并发阻塞,尽量只包含核心的查询和更新操作。
- 异常处理:必须捕获所有可能的异常(
\Exception或\Throwable),否则事务可能无法正常回滚,导致数据不一致。 - 数据库兼容性:
SERIALIZABLE级别在所有数据库中都支持,但部分轻量数据库(如 SQLite)对隔离级别的支持有限,需提前测试。 - 不要嵌套事务:YII2 中的嵌套事务是「模拟事务」(基于保存点),隔离级别设置仅对最外层事务有效,尽量避免嵌套事务设计。
总结
- YII2 中推荐使用框架封装的隔离级别常量(
Transaction::ISOLATION_xxx),无需写原生 SQL,兼容性更好。 - 事务使用分两种场景:复杂业务用「手动控制事务」(灵活),简单业务用
db->transaction()快捷方法(简洁)。 - 隔离级别选择:大多数业务用「读已提交」,需要稳定查询结果用「可重复读」,强一致性低并发场景用「串行化」。
- 核心要点:事务内必须包含完整的查询-更新逻辑,异常时务必回滚,避免长时间占用事务锁。
版权属于:Joyber
本文链接:https://blog.qqvbc.com/default/1422.html
转载时须注明出处及本声明