Laravel 5.1迁移切换PostgreSQL遇外键约束删除失败问题
首先得明确一个关键点:你设置的->onDelete('cascade');是数据行删除时的级联规则——意思是当你删除table1或table2里的某一行数据时,table1_table2中关联的行会被自动删除。但它完全不影响删除整个表的操作,PostgreSQL在删除表时会严格检查所有指向该表的外键约束,只要有约束存在,就会阻止表删除,这和MySQL的SET FOREIGN_KEY_CHECKS = 0逻辑不一样。
接下来看你遇到的具体问题:
为什么会报错?
你的错误提示显示,删除table2时,table1_table2表上的外键约束还依赖它。这大概率是因为你的外键迁移的down方法没有正确删除外键约束——Laravel在PostgreSQL下删除外键的方式和你写的不一样。
看你外键迁移的down代码:
$table->dropForeign('table1_id'); $table->dropForeign('table2_id');
这里直接传字符串列名是错误的:Laravel的dropForeign方法如果传字符串,会认为你要删除名称为该字符串的外键,但PostgreSQL中Laravel自动生成的外键名是类似table1_table2_table1_id_foreign这种格式,不是单纯的列名。所以这段代码实际上没删掉任何外键,导致回滚时约束还存在,删除table2就会失败。
解决办法
1. 修正外键迁移的down方法,正确删除外键
有两种方式可以正确删除外键:
- 方式一:用数组传递列名(推荐,不用记复杂的外键名称)
Laravel会根据表名和列名自动生成对应的外键名称,帮你删除:
public function down() { ... Schema::table('table1_table2', function(Blueprint $table) { $table->dropForeign(['table1_id']); $table->dropForeign(['table2_id']); }); ... }
- 方式二:直接指定外键的完整名称
如果你知道外键的具体名称(比如错误提示里的table1_table2_table1_id_foreign),可以直接传这个名称:
public function down() { ... Schema::table('table1_table2', function(Blueprint $table) { $table->dropForeign('table1_table2_table1_id_foreign'); $table->dropForeign('table1_table2_table2_id_foreign'); }); ... }
2. (备选)删除表时使用CASCADE强制删除
如果不想修改外键迁移,也可以在删除table2时用原生SQL加上CASCADE,强制删除所有依赖对象:
public function down() { DB::statement('DROP TABLE IF EXISTS table2 CASCADE'); // 替换原来的 Schema::drop('table2'); }
不过这种方式要谨慎,因为它会直接删除所有依赖table2的对象,可能会不小心删掉你不想删的东西,优先推荐第一种方法。
3. 确认迁移回滚顺序
你的迁移顺序是对的:外键迁移最后执行,所以回滚时会先执行外键迁移的down,再执行table2的down,最后执行table1的down。只要外键被正确删除,table2就能顺利删除了。
内容的提问来源于stack exchange,提问作者user3871

