$model->save() 返回 true 却未更新数据库最常见原因是字段未在 rules() 中声明为 safe 或其他有效规则,导致批量赋值时被忽略,虽 save() 成功但 SQL 不包含该字段。

为什么 $model->save() 返回 true 却没更新数据库
最常见原因是字段没进 rules() 或没标为 safe。Yii2 默认只允许“安全属性”被批量赋值,比如用 $model->attributes = $data 或 $model->load($data) 时,如果新字段没在 rules() 里声明,它会被直接忽略——save() 看起来成功(返回 true),但 SQL 实际没包含那个字段。
检查方式:打印 $model->getDirtyAttributes(),它会返回真正被标记为“已变更”的字段。如果新加的字段不在里面,说明它根本没进模型属性栈。
- 补救方法:在模型类的
rules()中加上该字段,类型可先用'safe'(如['new_field', 'safe']) - 或临时绕过验证:用
$model->save(false),但仅限调试,上线前必须补规则 - 别依赖
save(false)当万能解——它跳过验证也跳过属性过滤,如果字段名拼错或类型不匹配,依然不会写入
updateAll() 和 save() 的适用边界在哪
updateAll() 是直接发 SQL,不走模型生命周期(不触发 beforeUpdate、不校验、不格式化),适合批量改、条件明确、无业务逻辑的场景;save() 走完整 AR 流程,适合单条记录、需校验、要触发事件或自动处理时间戳(如 updated_at)的情况。
- 用
updateAll()时,条件必须带 WHERE,否则全表覆盖:User::updateAll(['status' => 1], ['id' => 123]) -
save()前必须确保模型已加载(如User::findOne(123)),否则是插入新记录 - 注意时间戳字段:如果模型里定义了
updated_at自动填充,save()会更新它;updateAll()不会,得手动加进去
CLI 定时任务里更新失败的隐藏陷阱
CLI 模式下没有 session、没有 request 上下文,rules() 里的某些验证器(比如依赖 Yii::$app->request 的)可能静默失效,或者字段类型判断出错(如把 tinyint 当成 string 校验失败但没报错)。
- 先确认模型是否加载成功:
var_dump($model->getErrors()),别只看save()返回值 - 检查字段类型映射:Gii 生成的模型里,
tinyint(1)字段默认 rule 是'string',传整数会校验失败——改成'integer'或'safe' - 避免用
$model->attributes = $data直接赋值,改用$model->setAttributes($data, false)显式关闭场景校验(第二个参数为false表示不按场景过滤)
字段存在但 update 后值没变,可能是这些原因
不是代码问题,而是数据层行为:MySQL 的 UPDATE 语句如果 SET 的值和原值完全一致,影响行数为 0,Yii2 的 save() 和 updateAll() 都会返回 0 或 false,但不抛异常。
- 查日志:
Yii::info($model->getDirtyAttributes(), 'update')看哪些字段真变了 - 对比原始值:
$old = $model->getOldAttribute('status')vs$model->status - 注意字符串 trim 和 null 处理:数据库里是
NULL,PHP 里传'',MySQL 认为不同,但有些驱动会自动转,导致实际没更新
复杂点在于 dirty tracking 依赖 getOldAttribute() 快照,而快照只在 findOne() 或 refresh() 时生成——如果中间有人改过数据库,模型里的旧值就 stale 了。


















