laravel外键级联删除需显式配置:foreignid()->constrained()->ondelete('cascade'),确保字段类型一致、加索引、使用innodb引擎,sqlite需手动启用外键,软删除需用模型事件替代数据库级联。

外键级联删除在 Laravel 迁移中必须显式启用
默认情况下,Laravel 的 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() - 外键字段必须加索引:Laravel 7+ 的
foreignId()默认加索引;但若手动写unsignedBigInteger('user_id'),需额外调用->index() - MySQL 引擎必须为 InnoDB(MyISAM 不支持外键);检查迁移前执行
DB::connection()->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)确认
如何在迁移中正确设置 onDelete('cascade')
onDelete('cascade') 只是告诉 Laravel 在生成 SQL 时加上 ON DELETE CASCADE 子句,它本身不触发任何 PHP 层逻辑。真正起作用的是数据库引擎的约束行为。
实操建议:
CAD通信网关公共库(装修设计扩展版)。提供统一CAD COM封装接口,支持AutoCAD/天正双模式,包含装修专业图层体系、材料图块、房间边界检测、弧形吊顶COM接口。复用建筑施工图方案Skill0公共库。
- 用
foreignId('user_id')->constrained()->onDelete('cascade')——constrained()自动推导表名和字段名,并隐式加索引 - 若关联字段名不标准(如
author_id指向users.id),写全:foreignId('author_id')->constrained('users')->onDelete('cascade') - 不要省略
constrained()或等效的references('id')->on('users'),否则onDelete()无效(Laravel 会跳过外键定义) - 示例迁移片段:
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' => [PDO::ATTR_EMULATE_PREPARES => true, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_PERSISTENT => false, PDO::ATTR_ORACLE_NULLS => PDO::NULL_TO_STRING],不够,还得在DB::connection()->getPdo()->exec('PRAGMA foreign_keys = ON');—— 建议放在AppServiceProvider::boot()中 - MySQL 8.0+ 默认开启,但老版本可能需要确认:
SHOW VARIABLES LIKE 'foreign_key_checks';返回ON - 测试时别只看迁移成功与否,务必手动插入数据并尝试删除父记录,验证子记录是否真的消失










