laravel默认不级联删除,需在迁移中用ondelete('cascade')配置数据库外键约束;软删除、跨库或旧系统限制时才考虑模型事件兜底,但需注意事务一致性与性能问题。

delete() 时外键没清空,关联数据还在
这是最常见错觉:以为调用 $model->delete() 就会自动删掉所有子记录。Laravel 默认不会做任何级联操作——它只删当前模型,其他全靠你手动处理。
原因很简单:delete() 是 Eloquent 的软删除或硬删除入口,但关联关系本身是“声明式”的,不触发动作。除非你显式配置了数据库级约束或 Eloquent 事件监听。
- 检查迁移里有没有加
onDelete('cascade')—— 没加就只是 PHP 层面的“知道有关系”,数据库根本不理 - 如果用了软删除(
SoftDeletes),子表没启用的话,forceDelete()也不会级联 - 别依赖模型事件(如
deleting)去手动删子记录,容易漏掉事务回滚或异常中断
用 foreign key constraints 实现真级联删除
最稳的方式是把级联逻辑交给数据库,MySQL/PostgreSQL 都支持,Eloquent 只负责执行 SQL。
关键在迁移里定义外键时加约束:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained()->onDelete('cascade');
});
注意点:
-
onDelete('cascade')是数据库行为,不是 Laravel 行为;删父记录时,DBMS 自动删子记录,Eloquent 完全不感知 - MySQL 要求关联字段类型严格一致(比如都是
BIGINT UNSIGNED),否则constrained()可能静默失败 - SQLite 不支持
ON DELETE CASCADE,本地测试通过不代表上线能用 - 如果表已存在,得先
dropForeign()再重新 add,不能直接改
想置空外键而不是删子记录?用 onDelete('set null')
有些场景下,你希望删用户时保留帖子,只是把 user_id 设为 NULL。这比级联删除更安全,也更容易审计。
前提是字段允许为空:
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
常见陷阱:
- 忘了加
nullable(),迁移报错 “Cannot add or update a child row” - 字段是
unsignedBigInteger但没设nullable(),即使写了onDelete('set null')也没用 - Laravel 10+ 中,
foreignId()默认非空,必须显式调用nullable() - 置空后,Eloquent 查询时若用
with('user'),关联模型会是null,别假设一定有值
用模型事件模拟级联?小心事务和性能
当数据库约束不可用(比如跨库、旧系统不允许改表),才考虑用 Eloquent 事件兜底。但这是次选方案,不是默认解法。
典型写法:
protected static function booted()
{
static::deleting(function ($post) {
$post->comments()->delete();
});
}
问题比看起来多:
- 子查询没走事务:主模型删了,子模型删到一半出错,数据不一致
- 没加
withTrashed()的话,软删除模型的子记录可能被跳过 - 嵌套太深(比如评论还有回复)容易 N+1 或爆内存,建议用
chunkById()分批删 - 事件监听器里别再触发另一个模型的
delete(),可能形成隐式递归
真正要处理复杂依赖时,与其堆事件,不如写个带事务的手动服务类,明确控制顺序和回滚点。











