ThinkPHP 6.0 迁移报 errno 150 是因 MySQL 外键约束失败,根源在于关联表未建、字段类型不匹配或引擎非 InnoDB;需确保字段类型一致、被引用列有索引、显式指定 ENGINE=InnoDB,并优先用 foreignId()->constrained() 封装方法。

ThinkPHP 6.0 数据库迁移中执行 php think migrate:run 报错 errno 150,不是迁移文件写错了,而是 MySQL 在建表时发现关联表未创建、字段类型不匹配或存储引擎不是 InnoDB;直接调换迁移文件顺序不能根治,尤其在团队协作、多分支合并、refresh 回滚等场景下会反复触发。
先确认外键是否真生效
执行 SHOW CREATE TABLE orders; 查看输出里有没有 FOREIGN KEY 字样,且 ENGINE=InnoDB;如果只有 ENGINE=MyISAM 或压根没 FOREIGN KEY,说明外键根本没建成功——ThinkPHP 不解析外键,它只生成 SQL,最终由 MySQL 执行并校验。
MyISAM 引擎会静默忽略 FOREIGN KEY 语句,也不报错,但约束形同虚设。
若已有表是 MyISAM,运行 ALTER TABLE `orders` ENGINE=InnoDB; 补上引擎。
迁移中正确声明外键的三要素
缺一不可:字段类型严格一致 + 被引用列有索引(主键自动满足)+ 显式指定 ENGINE=InnoDB。
方法一:用 ThinkPHP 封装方法(推荐)
在 up() 中写:
$table->foreignId('user_id')->constrained();
这会自动生成 unsignedBigInteger('user_id') 并绑定 users.id,前提是 users 表已存在且 id 是 bigIncrements。
方法二:手动拼字段 + 外键(需自行核对)
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
【注意:users 表必须已在本次 migrate 之前执行过 up(),否则报 errno 150】
方法三:跨表依赖强时,拆成两个迁移文件
第一步:20260701000000_create_users_table.php → 建 users 表
第二步:20260701000001_create_orders_table.php → 建 orders 表并加外键
时间戳必须严格递增,不能靠改名欺骗扫描顺序。
绕过外键检查的临时方案
第一步:在 up() 开头插入
$this->execute('SET FOREIGN_KEY_CHECKS = 0');
第二步:执行建表与外键逻辑
(此处写你的 $this->table(...)->create() 或其他结构操作)
第三步:在 up() 结尾插入
$this->execute('SET FOREIGN_KEY_CHECKS = 1');
down() 方法里也必须成对出现,否则回滚时因外键限制失败;【千万别在 config/database.php 全局关 foreign_key_checks,会影响所有模型操作】
这一步操作起来很简单,直接把 execute 语句贴进去就行。
排查 errno 1215 / 1452 的速查路径
① 查字段类型是否对齐:
DESCRIBE users; 和 DESCRIBE orders; 对比 id 与 user_id 的 Type、Null、Key 列;INT 不能对 UNSIGNED BIGINT。
② 查字符集是否统一:
SHOW CREATE TABLE users; 和 SHOW CREATE TABLE orders; 看 DEFAULT CHARSET 是否都是 utf8mb4;混用 utf8 和 utf8mb4 会触发 1215。
③ 查数据是否干净(仅限 down 后再 up 场景):
SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users);
若有结果,说明残留脏数据,需先清理或加 WHERE EXISTS 过滤。
云数据库如阿里云 RDS 默认禁止 SET FOREIGN_KEY_CHECKS,会报 ERROR 1227 (42501),此时只能联系 DBA 开放权限,或改用 exists 判断 + 条件建表。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











