Laravel如何做数据库迁移外键级联行为_LaravelonDelete设置CASCADE【指南】
Laravel迁移中外键级联删除需显式设置,用foreignId()->constrained()->onDelete('cascade'),字段类型一致、加索引、使用InnoDB。软删除时数据库级联失效,改模型事件处理。SQLite需手动开启外键约束。
在Lara vel里,外键级联删除并不是开箱即用的——你得显式告诉框架你要做什么。核心配置就是foreignId()->constrained()->onDelete('cascade')这一串。但光写这行代码还不够,还要确保字段类型一致、加了索引、用的是InnoDB引擎(SQLite的话得手动开外键)。如果碰上软删除,数据库级联就跑偏了,得换模型事件来处理。

外键级联删除在 Lara vel 迁移中必须显式启用
需要明确的是,Lara vel的foreignId()或unsignedBigInteger()并不会默认给你加上ON DELETE CASCADE。即便你写了->onDelete('cascade'),底层引擎不买账,或者迁移里约束名字没写对,照样不会生效。
你是不是也碰到过这种报错?SQLSTATE[HY000]: General error: 1005 Can't create table `xxx` (errno: 150 "Foreign key constraint is incorrectly formed")——十有八九是字段类型不匹配或索引没加。
- 首先,关联字段的类型必须一模一样。比如主表的
id是bigIncrements(),外键就得用foreignId()(等价于unsignedBigInteger()),千万别用integer()。 - 外键字段一定要加索引。Lara vel 7+的
foreignId()默认自带索引,但如果你手动写成unsignedBigInteger('user_id'),记得补上->index()。 - MySQL引擎必须用InnoDB(MyISAM不支持外键)。不确定的话,可以在迁移前跑一下
DB::connection()->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)来确认。
如何在迁移中正确设置 onDelete('cascade')
onDelete('cascade')本质上是告诉Lara vel在生成SQL时附带上ON DELETE CASCADE子句,它本身并不触发任何PHP层的逻辑。真正起作用的是数据库引擎的约束行为。
实操建议:
- 直接使用
foreignId('user_id')->constrained()->onDelete('cascade')——constrained()会自动推导表名和字段名,并隐式加上索引,省心。 - 如果关联字段名不标准(比如
author_id指向users.id),那就写全:foreignId('author_id')->constrained('users')->onDelete('cascade')。 - 千万不要省略
constrained()或等价的references('id')->on('users'),否则onDelete()会直接失效(Lara vel会跳过外键定义)。 - 示例迁移片段:
Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('title'); });
软删除模型 + 外键级联 = 需要额外处理
如果主表用了SoftDeletes,ON DELETE CASCADE不会触发软删除,而是直接物理删除子记录——这往往不是你想要的效果。
这个时候数据库级级联就失效了,必须改用模型事件或观察者:
- 在父模型中监听
deleting事件,手动调用子模型的delete()(这样就能触发软删除)。 - 或者用
forceDelete()显式物理删除,同时确保子模型也允许强制删除。 - 避免混用:不要一边设
ON DELETE CASCADE,一边又在模型里写deleting回调,否则子记录会被删两次(第二次会报错)。
SQLite 和 MySQL 的行为差异容易被忽略
SQLite默认不启用外键约束,所以即使迁移里写了onDelete('cascade'),运行时也会静默失效。
必须在连接建立后手动开启:
- 在
config/database.php的SQLite配置里,光加'options' => [...]还不够,还得在AppServiceProvider::boot()中执行一句DB::connection()->getPdo()->exec('PRAGMA foreign_keys = ON');。 - MySQL 8.0+默认开启外键检查,但老版本最好确认一下:
SHOW VARIABLES LIKE 'foreign_key_checks';,返回ON才算正常。 - 测试时别只看迁移是否成功,务必手动插入数据并尝试删除父记录,验证子记录是否真的被级联处理了——这才是真正管用的验证方式。


































