Laravel 8执行迁移报General error:1215无法添加外键约束
核心报错信息:
SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (SQL: alter table `subjekat_adrese` add constraint `subjekat_adrese_subjekti_id_foreign` foreign key (`subjekti_id`) references `subjekti` (`id`) on delete cascade)
你尝试的两种外键定义写法本身没有语法错误,报错是因为外键约束的底层规则没有被满足,按以下优先级排查即可解决:
1. 迁移执行顺序错误(90%概率为该原因)
Laravel按照迁移文件名前缀的时间戳顺序执行迁移,你当前创建subjekat_adrese表的迁移时间戳为2022_06_06_114027,必须保证被关联的4张表(subjekti/drzave/gradovi/poste)的迁移文件时间戳早于该值,否则执行当前迁移时被关联表还未创建,外键必然添加失败。
- 修复方式:修改4张被关联表迁移文件的时间戳,使其早于
2022_06_06_114027,保证父表先创建、子表后创建。 - 验证方式:执行
php artisan migrate:status可查看所有迁移的执行顺序,确认4张父表排在当前迁移之前。
2. 关联字段类型不匹配
你当前定义外键字段用的是$table->bigInteger(),该方法生成的是有符号BIGINT类型,而Laravel默认主键$table->id()/$table->bigIncrements('id')生成的是无符号BIGINT类型,字段属性不匹配会直接触发1215错误。
- 修复方式:将所有外键字段改为无符号类型,替换原有字段定义:
// 替换原有的bigInteger定义 $table->unsignedBigInteger('subjekti_id')->index(); // 以下三个字段因为用了SET NULL规则,必须加nullable() $table->unsignedBigInteger('drzave_id')->nullable()->index(); $table->unsignedBigInteger('gradovi_id')->nullable()->index(); $table->unsignedBigInteger('poste_id')->nullable()->index();
更简便的写法:直接用Laravel提供的外键快捷方法,自动匹配父表主键类型,避免类型错误:
// 不需要单独定义字段、不需要单独写foreign约束,一行搞定 $table->foreignId('subjekti_id')->constrained()->onDelete('cascade'); $table->foreignId('drzave_id')->nullable()->constrained()->onDelete('SET NULL'); $table->foreignId('gradovi_id')->nullable()->constrained()->onDelete('SET NULL'); $table->foreignId('poste_id')->nullable()->constrained()->onDelete('SET NULL');
3. 外键规则与字段属性冲突
你给drzave_id/gradovi_id/poste_id设置了onDelete('SET NULL')规则,意味着父表记录删除时,对应外键字段会被置为NULL,但你原有代码里这三个字段没有加->nullable(),默认不允许为空,规则冲突也会导致外键创建失败,这一点在上面的修复代码里已经加上了nullable()对应解决。
4. 数据库引擎/排序规则不统一
- 确认所有关联表都使用
InnoDB引擎,MyISAM引擎不支持外键约束。可以在config/database.php中配置mysql连接默认引擎为InnoDB,也可以在单张表迁移里加$table->engine = 'InnoDB';手动指定。 - 确认所有关联字段、被关联字段的字符集排序规则一致,不要出现同名字段跨表排序规则不统一的情况。
额外代码问题提示
你给primarna字段直接加了->unique()约束,这会导致整张表只能存在一条primarna = 1的记录,后续给同一个主体添加第二条地址就会触发唯一键冲突。如果业务需求是每个主体只能有一个主地址,应该改成联合唯一约束:
$table->boolean('primarna'); // 联合唯一键:同一个subjekti下只能有一个primarna=1的记录 $table->unique(['subjekti_id', 'primarna']);
所有修改完成后,本地测试可以执行php artisan migrate:fresh清空所有表重新执行迁移(注意:该操作会删除数据库所有数据,绝对不能在生产环境执行),即可正常完成迁移。
内容的提问来源于stack exchange,提问作者Ivan

