必须手动开启事务以确保多模型操作的原子性;ActiveRecord单个save/delete不启用事务,仅当两个及以上模型写操作需“全成功或全失败”时才显式包裹事务,如用户注册同时写入User和UserProfile表。

在 Yii 应用中执行多模型联合查询或跨表写入时,若不加事务控制,极易出现用户信息已存但关联资料丢失、订单创建成功但库存未扣减等数据断裂问题。
何时必须手动开启事务
ActiveRecord 的单个 save() 或 delete() 默认不启用事务;只有涉及两个及以上模型的写操作,且要求“全成功或全失败”,才需显式包裹事务。
比如:用户注册同时写入 User 和 UserProfile 表,任一失败都应让另一方回滚。
Yii 不会自动为关联保存加事务——哪怕你调用了 $user->link('profile', $profile),底层仍是两条独立 SQL。
标准事务封装写法
第一步:调用 Yii::$app->db->beginTransaction() 获取事务对象。
第二步:在 try 块内依次执行模型验证与保存。注意:必须先 validate() 再 save(),否则 save() 失败时不会抛异常,事务无法捕获错误。
第三步:验证全部通过且保存成功后,调用 $transaction->commit() 提交。
第四步:任一环节出错,catch 中执行 $transaction->rollback() 并可记录日志或返回错误提示。
【务必检查 $model->save() 的返回值,它不抛异常却可能静默失败】
事务中读取旧数据做比对的正确姿势
方法一:在事务开启前,用 Yii::$app->db->slave(或主库)查出原始值并缓存到变量中,事务内直接比对变量值。
方法二:若必须在事务中查数据库(如校验唯一性),强制走主库:Yii::$app->db->master->createCommand(...)->queryScalar()。
注意:事务一旦开启,所有后续查询(包括 createCommand()->queryOne())都会被 Yii 强制路由到主库,Yii::$app->db->slave 的调用会被忽略。
避免嵌套事务踩坑
Yii 不支持真正的嵌套事务;内层 beginTransaction() 实际只是增加嵌套计数,最终只由最外层 commit() 或 rollback() 决定结果。
如果在已有事务的控制器动作里,又调用了另一个也开启事务的服务方法,两个 rollback() 会互相干扰——第二次 rollback 可能因事务已关闭而报错。
推荐做法:统一由最上层(如 controller action)开启事务,下层 service 方法只负责模型操作,不碰 beginTransaction()。
事务内触发事件与副作用的时机控制
方法一:使用 Transaction::onCommit() 注册回调,在事务真正提交后执行日志写入、消息推送等非数据库操作。
方法二:不要在 $model->save() 后立刻调用 $model->trigger(),否则事务回滚时事件已发出,造成状态不一致。
【onCommit 回调中不能开启新事务,否则会破坏当前事务边界】


















