结论:执行迁移前必须先运行php artisan migrate:status核对状态,再用--pretend预演SQL,生产环境务必加--force并分步执行;migrate:status可快速暴露漏跑、失败未报错、表存在但记录缺失三类问题,避免直接migrate报错后被动排查。

直接说结论:别一上来就 php artisan migrate,先看状态、再预演、最后执行;生产环境必须加 --force,且不能跳过 migrate:status 核对。
为什么 migrate:status 比 migrate 更值得先跑
它不改库,只查 migrations 表,但能立刻暴露三类问题:本地漏跑了别人提交的新迁移、某次迁移在你机器上失败却没报错(Ran? 显示 No)、或表已存在但记录缺失(常见于手动建表后忘了写迁移)。很多人跳过这步,结果 migrate 报 SQLSTATE[42S01]: Base table or view already exists 才回头翻日志——其实 migrate:status 一行就定位到是哪个文件卡住了。
--pretend 输出的 SQL 必须人工过一遍
它打印的是真实将要执行的语句,不是模板。重点检查:
- 字段类型是否和预期一致(比如
$table->string('phone', 20)生成VARCHAR(20),但若旧字段是VARCHAR(15),MySQL 会隐式转成TEXT导致索引失效) - 外键约束名是否重复(
foreign key users_company_id_foreign已存在时会报错,需在up()中加->constrained()或显式命名) - 大表
ALTER TABLE是否出现在高峰期(如百万级users表加字段,--pretend能让你看到ALTER TABLE users ADD COLUMN phone VARCHAR(20),这时就得切到维护窗口)
down() 不是摆设,删字段必须显式处理
Laravel 5.5 默认允许 down() 留空,但一旦你写了,就必须覆盖 up() 的全部副作用。典型坑点:
- 在
up()里用$table->string('phone')->unique()加了字段和唯一索引,down()只写$table->dropColumn('phone'),回滚再迁移就会报Index name users_phone_unique already exists - SQLite 和旧版 MySQL 不支持
dropColumn(),Laravel 5.5 会静默失败,down()里得改用DB::statement('ALTER TABLE ...')手动处理 - 如果迁移中创建了中间表(如
user_roles),down()必须调用Schema::dropIfExists(),否则migrate:rollback后表还在,下次migrate又建一遍
生产环境执行迁移前的硬性动作
这些不是可选项,是避免锁库/丢数据的底线:
- 确认
.env中DB_DATABASE指向目标库(曾有人在 staging 环境误连 production,--pretend看着没问题,一执行就全表重建) - 加
--force参数,否则 Laravel 5.5 会拦截生产环境的migrate命令并抛异常 - 用
php artisan migrate --step=1代替一次性全跑,尤其上线新字段时,逐个验证比批量回滚更可控 - 执行完立刻查
migrations表,确认新记录的batch值递增且无null,否则说明部分迁移未真正写入
最常被忽略的其实是时间戳——Laravel 5.5 的迁移文件名里那个 2014_10_12_000000 不只是排序依据,它决定了执行顺序。如果你手动生成了两个同秒级的文件(比如快速敲了两次 make:migration),Laravel 会按字母序执行,而不是你期望的逻辑顺序。真遇到这种事,别改时间戳重命名,直接删掉一个,用 --path 指定新位置重生成。


















