yii2事务必须显式commit()或rollback(),漏掉会导致连接挂起、锁表甚至连接池耗尽;需用try...catch包裹并确保回滚,禁止嵌套事务、非db操作及手动new transaction。

Yii2 中 beginTransaction() 必须配对 commit() 或 rollBack()
事务不是“开了就自动生效”,Yii 的 beginTransaction() 只是开启一个数据库级别的事务上下文,后续所有 save()、createCommand()->execute() 等操作会进入该事务,但最终是否持久化,完全取决于你显式调用 commit() 还是 rollBack()。漏掉任一者,轻则连接挂起、锁表,重则事务长期未释放导致数据库连接池耗尽。
常见错误现象:SQLSTATE[HY000]: General error: 2006 MySQL server has gone away 或应用卡在某次请求不动 —— 很可能就是事务没关,连接被占着。
- 必须用
try...catch包裹事务体,catch中必须调用$transaction->rollBack() -
commit()后不能再对该$transaction调用任何方法,否则抛出Exception: Trying to commit a transaction that is not active - 不要在事务中做 HTTP 请求、文件写入、日志落盘等非数据库操作,失败后无法回滚,造成数据不一致
控制器里别直接 new Transaction,要用 Yii::$app->db->beginTransaction()
Yii 的事务对象必须从当前 DB 组件获取,而不是手动 new \yii\db\Transaction。否则事务不会绑定到当前连接,save() 等操作走的是默认连接,根本不在你开的事务里 —— 表面看代码有事务,实际形同虚设。
使用场景:你在 SiteController 的某个 action 里要同时更新用户余额和生成订单记录,两个模型都需原子性保障。
正确写法:
$transaction = Yii::$app->db->beginTransaction();
try {
$user->balance -= 100;
$order->status = 'paid';
if (!$user->save() || !$order->save()) {
throw new Exception('保存失败');
}
$transaction->commit();
} catch (Exception $e) {
$transaction->rollBack();
throw $e;
}
嵌套事务在 Yii2 中不被支持,save() 内部再开事务会报错
MySQL 支持 SAVEPOINT,但 Yii2 的 beginTransaction() 默认不启用保存点模式。如果你在已开启事务的控制器里,又在某个模型的 beforeSave() 中调用 Yii::$app->db->beginTransaction(),会直接抛出 Exception: Cannot start transaction within a transaction。
参数差异:Yii::$app->db->beginTransaction(\yii\db\Transaction::SERIALIZABLE) 只影响隔离级别,不解决嵌套问题。
- 需要“局部回滚”时,改用
savepoint手动管理:$transaction->setSavepoint('sp1')和$transaction->rollBackTo('sp1') - 更稳妥的做法是把业务逻辑拆到服务层,由上层统一控制事务边界,控制器只负责调度
- 避免在 ActiveRecord 的生命周期钩子(如
afterSave)里触发新事务
事务里查数据要注意 refresh() 和脏读风险
事务开启后,同一连接内多次 find()->one() 返回的是事务快照里的数据,不是最新提交值。比如你先查用户余额是 500,接着扣了 100 并 save(),再查仍是 500 —— 这不是 bug,是 RR(Repeatable Read)隔离级别的正常行为。
容易踩的坑:用 $model->refresh() 强制重查,但它查的仍是当前事务内的版本;真要读其他事务已提交的新值,得用 Yii::$app->db->useMaster(true)->createCommand(...)->queryOne() 强制走主库新连接(慎用,破坏事务一致性)。
性能影响:长事务会拖慢 MVCC 清理,尤其在高并发更新场景下,可能引发 Lock wait timeout exceeded。
所以,事务体越短越好,查-改-存尽量一步到位,别在事务里反复 find() + save() 循环。
复杂点在于:事务不是开关,而是连接+状态+隔离级别的组合体;容易被忽略的是,它会悄悄改变你对“数据最新性”的直觉判断。











