Yii的delete()方法默认不级联删除关联表,仅删除主表记录;必须在beforeDelete()中手动处理关联数据,并用事务保证一致性。

delete() 方法默认不级联删除关联表
Yii 的 ActiveRecord::delete() 只删当前模型对应主表的记录,完全不管关联表。哪怕你定义了 hasMany() 或 hasOne() 关系,也不会自动触发任何关联数据清理。这是 Yii 的明确设计——避免隐式副作用,但也是新手最容易误以为“会自动删”的坑。
常见错误现象:Order::findOne(123)->delete() 执行后,order_items 表里对应的数据还在,导致后续查询出现脏数据或外键约束报错(如果数据库启用了 ON DELETE RESTRICT)。
- 必须显式处理关联数据,不能依赖 ORM 自动行为
- 若数据库外键设置了
ON DELETE CASCADE,可交由数据库层处理,但 Yii 不感知、不校验该行为 - 业务逻辑复杂时(如需记录删除日志、触发事件、做权限检查),数据库级级联反而不可控
在 beforeDelete() 中手动清理关联数据
最常用且可控的方式:重写模型的 beforeDelete() 方法,在主表记录删除前主动查出并删除关联记录。这个钩子在事务内执行,能保证原子性(前提是开启事务)。
示例(订单删除连带清空订单项):
public function beforeDelete()
{
if (!parent::beforeDelete()) {
return false;
}
// 先删关联项,再删主表
$this->getOrderItems()->each(function ($item) {
$item->delete();
});
return true;
}
- 注意调用顺序:先删子表,再让父类执行主表
delete(),否则可能因外键约束失败 -
each()避免一次性加载全部关联对象到内存;若数量极大,改用createCommand()->delete()直接 SQL 清理 - 若关联关系未定义(如只靠外键没写
getOrderItems()),就得手写OrderItem::deleteAll(['order_id' => $this->id])
使用事务包裹 delete() 调用
即使写了 beforeDelete(),也不代表绝对安全。如果应用层没开事务,主表删成功但关联表删失败,就会留下不一致状态。
正确做法是在业务层显式开启事务:
$transaction = \Yii::$app->db->beginTransaction();
try {
$order = Order::findOne(123);
$order->delete(); // 触发 beforeDelete,删 order_items
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
- 不要依赖模型内部自动开事务 —— Yii 的 ActiveRecord 不自动管理跨模型事务
- 如果关联删除逻辑分散在多个模型(比如订单 → 订单项 → 商品库存扣减),所有操作必须包在同一个事务里
- 长事务可能影响并发性能,简单一对一/一对多场景够用;高并发下建议异步清理 + 状态标记
软删除场景下关联数据怎么处理
如果主表用的是软删除(比如加 is_deleted 字段),delete() 实际只是更新状态,此时 beforeDelete() 仍会触发,但语义已变:你可能不想真删关联表,而是统一打标或忽略。
- 判断是否为软删除:
if ($this->isAttributeChanged('is_deleted') && $this->is_deleted) - 软删主表时,关联表通常也应软删(保持语义一致),或至少加索引字段(如
order_id+is_deleted)便于过滤 - 硬删和软删逻辑混用时,务必在
beforeDelete()里区分路径,否则容易误删真实数据
关联数据的生命周期必须和主表对齐,而对齐方式不是框架决定的,是你在每个 beforeDelete() 里写清楚的。漏掉一个分支,就可能在线上留下几个月都发现不了的孤儿记录。


















