Laravel迁移问题核心在于文件时间戳与migrations表记录不一致、外键约束默认禁用、MigrationServiceProvider移除及migrate:fresh清表逻辑激进,需严格校验状态、显式启用约束、更新服务注册并禁用生产环境fresh命令。

迁移文件名时间戳不一致导致 php artisan migrate 重复执行
Laravel 从 5.x 到 10.x,迁移文件名格式始终是 YYYY_MM_DD_HHMMSS_create_users_table.php,但底层对时间戳的解析逻辑没变,真正出问题的是开发者手动改名、复制粘贴迁移文件、或跨环境同步时删了部分文件——这时 migrations 表里记录的 batch 和磁盘上文件时间戳对不上。
常见错误现象:php artisan migrate 反复提示“nothing to migrate”,但查表发现某些迁移明明没跑;或者相反,报错说某迁移已存在(SQLSTATE[42S01]: Base table or view already exists)。
- 别手动重命名迁移文件,尤其别删掉前缀时间戳只留描述——Laravel 靠它排序和去重
- 跨版本升级(比如从 Laravel 8 升到 10)前,先运行
php artisan migrate:status确认所有迁移状态一致 - 如果必须补漏迁移,用
php artisan migrate --path=database/migrations/2020_01_01_000000_create_posts_table.php指定单个文件,而不是靠时间戳猜顺序
Schema::create() 在 Laravel 9+ 默认禁用外键约束
Laravel 9 开始,Schema::create() 内部默认调用 disableForeignKeyConstraints(),这是为了防止建表时因依赖顺序出错而中断。但很多人没意识到:这会让迁移成功后,外键其实并没真正生效。
使用场景:你写了 $table->foreignId('user_id')->constrained()->onDelete('cascade'),迁移跑完查 SHOW CREATE TABLE posts,发现根本没生成 FOREIGN KEY 定义。
- 显式启用约束:在迁移里加
Schema::enableForeignKeyConstraints();(通常放在up()结尾) - 注意 MySQL 版本兼容性:低于 5.7.16 的 MySQL 不支持
ON DELETE CASCADE对某些存储引擎(如 MyISAM) - Laravel 10 中该行为未改变,但
Schema::table()默认仍不自动启约束,改结构加外键时同样要手动开
从 Laravel 5.8 升到 6+ 后 MigrationServiceProvider 被移除
老项目如果自定义过迁移命令(比如重写 MigrateCommand 或注入自己的 MigrationRepositoryInterface),升级到 Laravel 6 会直接报错:Class Illuminate\Database\MigrationServiceProvider not found。这不是 bug,是框架把迁移服务彻底收归 DatabaseServiceProvider 管理了。
常见错误现象:升级后 php artisan migrate 报 Target class [migrate] does not exist,或者自定义迁移逻辑完全失效。
- 删掉
config/app.php里的Illuminate\Database\MigrationServiceProvider::class(如果还留着) - 自定义命令需继承
Illuminate\Database\Console\Migrations\MigrateCommand,而不是老的抽象基类 - 如果用了第三方迁移增强包(如
laravel-migration-generator),确认它是否兼容 Laravel 6+ 的服务容器绑定方式
php artisan migrate:fresh 在 Laravel 8+ 会清空所有表,不管有没有迁移记录
这个命令看起来只是“重来一遍”,但实际行为比想象中激进:它不看 migrations 表,而是直接 DROP TABLE IF EXISTS 所有非系统表。Laravel 8 引入 --drop-views 和 --drop-types 参数后,风险进一步扩大。
使用场景:本地开发想快速重置数据库,结果上线前误在生产环境跑了带 --force 的 migrate:fresh,连配置表都丢了。
- 永远不要在任何非本地环境运行
migrate:fresh,哪怕加了--force - 替代方案:用
php artisan migrate:reset && php artisan migrate,至少保留migrations表里的执行痕迹 - Laravel 10 中新增
--seed参数,但不会自动跳过种子数据里的危险操作(比如清空用户表),得自己在DatabaseSeeder里加环境判断
跨版本迁移最麻烦的不是语法变化,而是那些没报错、却悄悄绕过约束或跳过检查的行为——比如外键没建、时间戳乱序、fresh 命令清表逻辑升级却不提醒。盯住 migrations 表和实际数据库结构的差异,比盯着文档版本号更管用。


















