yii2中activerecord::update()返回布尔值而非受影响行数,真实行数需用createcommand()->execute()或updateall()获取。

Yii2中update()方法返回值不是受影响行数
很多人以为调用 ActiveRecord::update() 会返回 MySQL 的 affected_rows,实际它只返回布尔值:true 表示至少有一列被修改(哪怕值没变),false 表示验证失败或无变更。这跟原生 SQL 的行为不一致,容易误判更新是否真正生效。
真正反映数据库层面“改了几行”的,得绕过 ActiveRecord 的封装,直接查执行后的底层 PDOStatement。
-
update()内部调用的是Command::execute(),而后者才返回整数型受影响行数 - 如果你用了事务,必须确保在
execute()后立刻获取,否则后续语句可能覆盖PDOStatement状态 - 批量更新(如
updateAll())可以直接拿到行数,但单模型更新需手动干预
用createCommand() + execute()手动执行UPDATE并取行数
最可靠的方式是跳过 ActiveRecord,自己拼 SQL 或复用其生成的条件,再通过 createCommand() 执行。这样能确保拿到原始 execute() 返回值。
例如更新用户邮箱并确认是否真改了:
$sql = 'UPDATE {{%user}} SET [[email]] = :email WHERE [[id]] = :id';
$command = Yii::$app->db->createCommand($sql, [
':email' => 'new@example.com',
':id' => 123,
]);
$affected = $command->execute(); // ← 这里是整数,比如 1 或 0
- 注意表名用
{{%user}}而不是硬编码,保持迁移兼容性 - 参数必须用命名占位符(
:email),避免 SQL 注入 - 如果条件不匹配任何行,
$affected就是0,这才是真正的“没更新到”
在ActiveRecord中安全获取update()后的实际影响行数
如果你仍想走 ActiveRecord::update() 流程(比如需要触发事件、验证、日志等),又想拿到真实行数,可以临时拦截其内部 Command 对象。
关键点在于:Yii2 的 update() 最终调用 updateInternal(),而该方法返回的就是 Command::execute() 的结果 —— 但它被上层包装掉了。所以得重写或钩住。
- 最轻量的做法是重载模型的
update()方法,在调用父类后读取$this->owner->db->getSchema()->getLastInsertID()不适用,那是给 INSERT 用的 - 正确做法是访问
ActiveRecord::updateInternal()返回值,但它是 protected,所以建议改用updateAll()配合主键条件(适用于你知道主键且不依赖事件的场景) - 或者干脆放弃单模型 update(),统一用
createCommand()+ 原生 SQL,尤其在做幂等更新或状态机流转时更可控
updateAll() 是最接近需求的内置方案
如果你不需要触发 beforeUpdate/afterUpdate 事件,也不依赖模型验证逻辑,updateAll() 是 Yii2 提供的、直接返回受影响行数的方法。
$affected = User::updateAll(
['status' => 2, 'updated_at' => time()],
['id' => 123]
);
// $affected 是 int,比如 1、0 或更多(批量时)
- 它不加载模型,不走验证,不触发事件,纯 SQL 更新,性能高
- 条件部分支持数组语法(
['id' => 123])或字符串('id = 123'),后者更灵活但需自行转义 - 注意:它绕过了所有 ActiveRecord 生命周期,别在需要审计日志或关联更新的场景硬套
真正难的不是“怎么拿行数”,而是想清楚你要的是 ORM 层的语义(有没有触发业务逻辑),还是数据库层的事实(到底改了几行)。两者目标冲突时,强行混合反而容易漏掉条件未匹配、字段值未变却误判成功这类边界情况。











