应使用 updateAll() 进行安全的部分更新,它直接执行 SQL 不触发事件、不校验规则;若需 afterUpdate 事件,则用 findOne() 加载后 save(false)。

updateAttributes() 会清空未传字段,别用它更新部分字段
Yii2 的 updateAttributes() 看似适合只改几个字段,但实际它会把没传的属性设为 null 或默认值(取决于模型规则),尤其对 datetime、integer 类型字段很危险。比如你只传 ['status' => 1],而模型里 updated_at 是 int 且没在数组里,它就会被覆盖成 0 —— 这不是“部分更新”,是隐式破坏。
真正安全的部分更新必须绕过模型验证和属性批量赋值逻辑,直接走底层 SQL 更新。
用 updateAll() 配合条件 + 字段数组最稳妥
updateAll() 是 ActiveRecord 提供的原生部分更新方式,不加载模型、不触发事件、不校验规则,只生成 UPDATE ... SET ... WHERE 语句。它天然符合“只改指定字段”的需求。
- 第一个参数是键值对数组,如
['status' => 1, 'updated_at' => time()],只更新这些列 - 第二个参数是 WHERE 条件,支持数组形式(如
['id' => 123])或字符串(慎用,易 SQL 注入) - 注意:
updated_at、version等需要手动写入,不会自动填充 - 返回值是影响行数,可用于判断是否更新成功(比如返回
0表示没找到匹配记录)
示例:
$rows = User::updateAll(
['status' => User::STATUS_ACTIVE, 'updated_at' => time()],
['id' => $id]
);
想触发 afterUpdate 事件?得手动 load + save(false)
如果业务逻辑依赖 afterUpdate 事件(比如发通知、清缓存),updateAll() 不会触发任何模型事件。此时只能走模型流程,但要避免覆盖其他字段:
- 先用
findOne()加载完整记录(确保数据最新) - 只给要改的属性赋值,比如
$model->status = 1; - 调用
$model->save(false),false参数跳过验证和属性过滤,防止其他字段被重置 - 注意:关联字段、只读属性、
safe规则仍可能干扰,务必确认模型中目标字段在rules()里允许被赋值
示例:
$model = User::findOne($id);
if ($model) {
$model->status = 1;
$model->updated_at = time();
$model->save(false); // 不验证,不过滤,只提交已改字段
}
批量更新多个 ID 时,WHERE 条件别拼数组漏掉 key
用 updateAll() 批量更新时,常见错误是把 ID 列表直接当 WHERE 条件:比如 ['id' => [1,2,3]] 写成 ['id' => [1,2,3]] —— Yii2 会正确转成 IN,但若写成 [1,2,3](没包 key),就会出错或静默失败。
正确写法只有两种:
- 单值:['id' => 123]
- 多值:['id' => [1,2,3]](Yii 自动识别为 IN)
- 复杂条件:['and', ['in', 'id', [1,2,3]], ['status' => 0]]
漏掉字段名 key 或类型不对,updateAll() 可能不报错但实际没更新任何行,排查时容易忽略 WHERE 构造问题。
核心就一点:部分更新的本质是控制 SQL 的 SET 子句,而不是靠模型层“省略字段”。绕过模型走 updateAll() 最干净;非要走模型,就用 save(false) 并确保加载的是当前最新数据——否则并发下旧值覆盖新值的风险比字段覆盖还难查。


















