yii2中批量更新首选updateall(),它生成纯sql、不加载模型、不触发事件;需用关联数组指定字段值,where条件不能为空,否则导致全表更新。

Yii2 中 updateAll() 只更新指定字段的写法
直接用 updateAll() 是最轻量、最安全的批量更新方式,它只生成纯 SQL 的 UPDATE ... SET ... WHERE,不加载模型、不触发事件、不校验规则——适合后台定时任务或数据修复场景。
关键点在于:第二个参数是 WHERE 条件,第一个参数必须是「字段名 => 值」的关联数组,不能传对象或表达式(除非显式用 new \yii\db\Expression())。
User::updateAll(['status' => 1, 'updated_at' => new \yii\db\Expression('NOW()')], ['id' => [101, 102, 103]])- 如果值来自其他字段计算(如
score = score + 10),必须用Expression:['score' => new \yii\db\Expression('score + 10')] - WHERE 条件支持嵌套数组,比如
['and', ['status' => 0], ['created_at' => $date]]
用 createCommand()->update() 手写更灵活的 SQL
当需要动态拼接字段、使用数据库函数(如 JSON_SET)、或跨表 JOIN 更新时,createCommand()->update() 比 updateAll() 更可控,且仍走 Yii 的 SQL 构建和参数绑定流程,避免 SQL 注入。
注意:它不自动加表前缀,需手动用 $this->db->tablePrefix 或 {{%user}} 语法;也不自动处理时间戳字段,得自己写 NOW() 或 UNIX_TIMESTAMP()。
$sql = 'UPDATE {{%user}} SET status = :status, updated_at = NOW() WHERE id IN (:ids)';$this->db->createCommand($sql)->bindValues([':status' => 2, ':ids' => [201, 202, 203]])->execute();- 若要更新 JSON 字段(MySQL 5.7+):
'extra_info' => new \yii\db\Expression("JSON_SET(extra_info, '$.verified', 1)")
别误用 save() 或 update() 做批量操作
对多个模型逐个调用 $model->save() 或 $model->update() 看似直观,但每条记录都触发一次查询(先查再更新)、一次验证、一次事件(beforeUpdate / afterUpdate),性能极差,还可能因事务或并发导致意外覆盖。
常见错误现象:foreach ($ids as $id) { $u = User::findOne($id); $u->status = 1; $u->save(); } —— 这会发 N 条 SQL,且中间任何一条失败都会中断,无法原子性回滚。
- 仅当必须触发模型逻辑(如日志记录、关联更新)时才考虑循环 + 事务封装
- 否则一律优先用
updateAll()或原生createCommand() - 如果真要用模型批量更新,至少用
User::updateAll()配合条件,而不是findOne()+save()
WHERE 条件为空时的危险行为
updateAll() 和 createCommand()->update() 都**不会**自动拒绝空 WHERE 条件。一旦漏写或传入空数组,就会变成全表更新,线上事故高发点。
例如:User::updateAll(['status' => 0], []) 会把整张用户表 status 全改成 0;['id' => []] 在某些版本中也会被忽略,等效于无条件。
- 上线前务必检查 WHERE 参数是否非空,建议加断言:
if (empty($where)) throw new InvalidArgumentException('WHERE condition cannot be empty'); - 开发期可开启 Yii 调试栏,观察实际执行的 SQL 是否含
WHERE - 生产环境建议在数据库侧加
SQL_SAFE_UPDATES=1(MySQL),强制要求 UPDATE/DELETE 必须带 KEY 条件











