activerecord save() 默认仅更新标记为“已更改”的字段,即使手动赋值相同内容也不会生成update sql;可通过手动设置dirtyattributes或直接使用createcommand()->update()强制全量更新。

ActiveRecord save() 默认跳过未改动字段
Yii 的 ActiveRecord::save() 默认只更新被标记为“已更改”的属性,哪怕你手动赋值了相同内容,只要没触发 dirty 标记,就不会生成 UPDATE SQL。这不是 bug,是设计行为——避免无意义的写操作和触发不必要的数据库钩子(如 updated_at 自动更新)。
强制所有字段都参与 UPDATE 的两种方式
如果你明确需要“不管有没有变,都执行 UPDATE”,不能靠改模型配置开关,得在调用时干预:
- 用
save(false):跳过验证,但依然只更新 dirty 字段,不满足需求 - 正确做法是先调用
$model->setAttributes($data, false),再手动标记所有目标字段为 dirty:$model->dirtyAttributes = array_keys($data); - 或者更稳妥:直接用
createCommand()->update(),绕过 AR 生命周期,例如:Yii::$app->db->createCommand()->update('user', $data, ['id' => $id])->execute();
TimestampBehavior 会干扰你的判断
如果你用了 TimestampBehavior,updated_at 字段会在每次 save() 时被自动重设,哪怕其他字段没变——这会让日志看起来像“执行了 UPDATE”,但实际 SQL 可能只更新了时间戳。检查真实 SQL 日志(开启 yii\db\Command::enableLogging = true)才能确认。
别依赖 getDirtyAttributes() 判断是否该 save()
这个方法返回的是当前 dirty 状态,但它不反映你刚 setAttributes() 后是否真的“有变化”。比如 $model->name = 'abc'; $model->name = 'abc'; 之后,getDirtyAttributes() 仍是空数组。真正触发 dirty 的是 __set() + 值比较逻辑,而比较发生在第一次赋值时。所以“重复赋相同值”不会让字段变 dirty,也就不会进 SQL。











