Yii框架不内置软删除,需手动实现;推荐用is_deleted字段+全局scope,默认排除已删记录,批量操作用updateAll()替代deleteAll(),关联软删除须事务手动控制。

Yii 框架本身不内置软删除机制,必须手动实现;直接调用 deleteAll() 或 delete() 都是物理删除,不会保留记录。
为什么不能依赖 delete() 方法做软删除
ActiveRecord 的 delete() 默认执行 SQL DELETE FROM,彻底移除行。即使你重写该方法,也容易漏掉关联操作(如 find()->with() 后的 delete)、批量操作(deleteAll())或命令行迁移中的硬删逻辑。
- 模型事件(
beforeDelete)里改 SQL 是危险的——框架仍会继续走后续清理流程,可能抛出主键不存在等异常 - 使用
updateAll()伪删除后,所有find()->where(...)查询默认仍会查出“已删”数据,需全局补条件 - 关系查询(如
$user->posts)不会自动过滤软删除项,除非显式配置viaTable或重写getPosts()
推荐做法:用 is_deleted 字段 + 全局 scope
在表中加 is_deleted tinyint(1) DEFAULT 0 NOT NULL 字段,然后在模型类中定义默认查询范围:
public static function find()
{
return parent::find()->where(['is_deleted' => 0]);
}
这样所有 User::find()->all()、User::findOne(1) 等调用自动排除已软删记录。
- 真正删除时改用
updateAll(['is_deleted' => 1], ['id' => $id]),不触发事件但安全可控 - 若需恢复,只需
updateAll(['is_deleted' => 0], ['id' => $id]) - 管理员查看全部数据?显式绕过 scope:
User::find()->from(['u' => User::tableName()])->all()或临时取消:parent::find()
deleteAll() 软删时最容易踩的坑
想用 deleteAll() 实现批量软删?别直接传条件数组进去——它底层仍会尝试生成 DELETE 语句,且不兼容 scope。
- 错误写法:
User::deleteAll(['id' => [1,2,3]])→ 物理删,不可逆 - 正确替代:
User::updateAll(['is_deleted' => 1], ['id' => [1,2,3]]) - 注意
updateAll()不会调用beforeSave或验证规则,所以字段类型、长度、NULL 约束必须提前确认 - 如果业务要求软删时同步更新
deleted_at时间戳,得手写 SQL 或拆成两步:createCommand()->update(...)->execute()
事务与级联软删除怎么处理
软删除不是原子操作,尤其涉及关联表时。比如删用户要同时软删其文章、评论:
- 不要靠外键 ON DELETE SET NULL —— 它只响应物理删除,对
updateAll()无效 - 必须在事务中手动控制:
$transaction = Yii::$app->db->beginTransaction(); try { User::updateAll(...); Post::updateAll(...); Comment::updateAll(...); $transaction->commit(); } catch (\Exception $e) { $transaction->rollBack(); throw $e; } - 避免在
afterSave或行为(Behavior)里触发关联软删——容易造成循环调用或 scope 干扰
最麻烦的是「恢复」操作:一旦某张子表没同步还原 is_deleted,数据就永远对不上。建议把软删/恢复封装成服务类,统一收口,别散落在各处 Controller 里。


















