关联模型更新必须先加载再修改并单独save,不可直接赋值;批量更新用updateAll()避免N次查询;跨表修改需事务保证一致性;字段名错误会导致条件失效,须校验SQL。

关联模型更新必须先加载再改,不能只改主模型属性
直接给 $model->relatedModel->name = 'new' 赋值不会触发保存,因为关联对象默认是只读的 AR 实例,没走验证、没标记为 dirty。常见错误是以为调用 save() 就能连带更新,结果数据库里啥都没变。
正确路径是:显式加载关联模型 → 修改其属性 → 单独调用 save()。比如用户有唯一 profile,要改 profile 的 bio:
$user = User::findOne(123);$profile = $user->profile; // 触发懒加载或已预加载$profile->bio = 'updated';$profile->save(); // 必须单独 save,且需检查返回值
注意:$profile 可能为 null(如一对一关系未创建),务必判空;save() 失败时不会抛异常,要靠返回布尔值判断。
批量更新关联表字段要用 updateAll(),别用 foreach + save()
想把所有用户的 profile.status 都设为 2,千万别写循环查出每个 $profile->status = 2; $profile->save();——这会生成 N 条 UPDATE,还带验证开销,1000 条就是 1000 次查询+事务+钩子。
直接用 DAO 的 updateAll() 一行搞定:
Yii::$app->db->createCommand()->update(
'profile',
['status' => 2],
'user_id IN (SELECT id FROM user WHERE type = :type)',
[':type' => 'premium']
)->execute();
关键点:
- 条件里用子查询或明确 ID 列表,避免全表扫
- 不经过模型层,跳过验证、事件、类型转换,纯 SQL 执行
- 如果关联表有外键约束或触发器,确认 SQL 不会意外触发副作用
更新时保持主从模型事务一致性,尤其跨表修改
业务要求“改用户邮箱同时更新其 profile 的 verified_at”,两个表都要动,但要么都成功、要么都失败。只对各自模型分别 save() 无法保证原子性。
必须包事务:
- 用
$transaction = \Yii::$app->db->beginTransaction(); - 依次调用
$user->save()和$profile->save() - 任一失败就
$transaction->rollBack(),成功才$transaction->commit()
注意:save() 返回 false 时不自动 rollback,得手动捕获;若中间用了 updateAll() 或原生 SQL,也要确保它们在同一个事务上下文中执行(即命令对象需复用同一连接)。
关联字段名写错会导致 updateAll() 条件失效却不报错
比如 profile 表实际外键字段叫 uid,但你在 updateAll() 条件里写了 'user_id IN (...)',MySQL 会静默忽略这个条件(当成恒真),结果可能误更新整张表。
排查方法:
- 先用
createCommand()->getRawSql()打印完整 SQL,人工核对字段名是否与数据库一致 - 在测试环境加
WHERE 1=0先跑 dry-run - 生产环境严禁无 WHERE 的
updateAll(),哪怕只是临时调试
最易被忽略的是大小写和下划线习惯差异——开发库用 user_id,但线上库可能是 userid,字段名不校验就上线,更新逻辑会彻底失控。


















