findOne()获取AR实例再save()是最安全的单条更新方式,能触发验证、事件和软删除逻辑;updateAll()直接执行SQL,不走模型层,需手动处理软删除和时间戳。

用 findOne() 获取模型再调用 save()
这是更新单条记录最标准、最安全的方式,适用于需要触发验证、事件或软删除逻辑的场景。
常见错误现象:直接写 User::updateAll() 却忘了它不走模型层,结果绕过了 beforeSave、字段类型转换、deleted_at 判断,导致逻辑删除的记录也被改了。
- 第一步:用
User::findOne($id)获取 AR 实例(返回null时必须判空,不能直接调属性) - 第二步:修改属性,例如
$user->status = 2 - 第三步:调用
$user->save()—— 它会自动判断是 insert 还是 update,基于isNewRecord属性 - 如果想跳过验证(比如后台强制更新),传
false:$user->save(false)
update() 和 save() 的区别在哪
两者都能更新已有记录,但语义和触发点不同:前者只做更新动作,后者是“保存当前状态”,会先检查是否新记录。
使用场景:你已经确定对象是从数据库查出来的(比如 findOne 或 find()->one()),且不需要插入逻辑,用 update() 更直白;但如果后续可能复用同一对象做插入或更新混合操作,统一用 save() 更稳妥。
-
$user->update()等价于$user->save(false),不校验、不触发beforeValidate -
$user->save()默认开启验证,失败时返回false,可通过$user->getErrors()查原因 - 注意:
update()不会重设时间戳字段(如updated_at),除非你在行为(TimestampBehavior)里配了自动更新
什么时候该用 updateAll() 而不是模型方法
当你明确不需要任何模型层干预——不验证、不触发事件、不处理软删除、也不需要读取旧值——就该用静态批量方法,它直接发 SQL,快且轻量。
容易踩的坑:误以为 User::updateAll(['status' => 1], ['id' => $id]) 和 findOne()->save() 效果一样。其实前者完全跳过 AR 生命周期,哪怕你模型里定义了 deleted_at 字段,它也会无条件更新。
- 适用场景:后台脚本、数据迁移、紧急修复、统计字段批量修正(如
view_count加 1) - WHERE 条件必须严格,建议用数组格式:
['id' => $id],避免字符串拼接引发 SQL 注入 - 它返回影响行数(int),可用于判断是否真有记录被改,例如
if (User::updateAll(...) === 0) { /* 未找到 */ }
别漏掉软删除和时间戳的隐性依赖
很多项目开了 SoftDeleteBehavior 或 TimestampBehavior,但开发者常在 updateAll() 或原生 DAO 里忽略它们的存在。
例如,一个用户已被软删除(deleted_at IS NOT NULL),你用 findOne($id)->save() 是安全的,因为 AR 默认加了 AND deleted_at IS NULL 条件;但 updateAll() 不会加,结果把已删除用户的字段又改回去了。
- 查单条+更新,优先走 AR 实例链路,这是唯一能保证行为一致的方式
- 如果非要用
updateAll(),就得手动补上软删除条件:['and', ['id' => $id], ['deleted_at' => null]] - 时间戳字段(
updated_at)不会被updateAll()自动更新,得显式写进第一个参数数组里


















