生产环境执行迁移必须加--force,否则Laravel会因APP_ENV=production中止操作;需配合migrate:status、--pretend预检、缓存清理及跨库SQL比对确保一致性。

生产环境执行迁移必须加 --force
不加 --force,Laravel 在 APP_ENV=production 下会直接中止迁移并提示 “Application in production mode” —— 这不是 bug,是硬性安全拦截。本地开发时没这限制,容易忽略,一上生产就卡住。
常见错误现象:运行 php artisan migrate 后只输出一行警告,表结构毫无变化,日志里也无报错。
- 必须显式执行
php artisan migrate --force - CI/CD 脚本里漏掉这个参数,会导致部署后数据库结构缺失,API 直接 500
-
--force不影响迁移逻辑,只绕过环境确认,该回滚的仍可migrate:rollback
同一套迁移文件在 MySQL 和 PostgreSQL 上字段类型不一致
longText() 在 MySQL 生成 LONGTEXT,但在 PostgreSQL 可能变成 TEXT(甚至被 Laravel 自动降级为 VARCHAR(255)),这不是配置错误,而是 Schema Builder 的适配策略差异。
根本原因:PostgreSQL 没有 LONGTEXT 类型,Laravel 会 fallback 到 TEXT;但若你项目里混用了 string('desc', 65535) 这类写法,在 PostgreSQL 会因超出 VARCHAR 长度上限而静默失败或截断。
- 跨数据库务必统一用语义化方法:
text()、longText()、json(),避免指定长度 - 检查
config/database.php中对应连接的'strict' => true是否启用,禁用 strict 模式会让 PostgreSQL 更激进地降级类型 - 用
php artisan migrate --pretend分别在两个环境跑一次,对比输出 SQL,确认实际建表语句是否一致
migrate:status 显示 Y 却查不到字段?可能是缓存没清
尤其 Laravel 9.22+ 在生产环境默认启用迁移状态缓存,migrate:status 返回的 “Y” 状态可能来自缓存,而非真实数据库记录表 migrations 的最新数据。
典型表现:改了迁移文件内容(比如把 string() 改成 longText()),重跑 migrate --force,字段还是旧类型。
- 先清三样:
php artisan migrate:clear(清迁移记录缓存)、php artisan config:clear、php artisan cache:clear - 再手动查数据库:
SELECT * FROM migrations WHERE migration LIKE '%your_migration_name%';,确认记录存在且batch值最新 - 如果记录存在但字段未更新,说明迁移文件被跳过——大概率是时间戳比已有记录旧,或文件名被误改导致 Laravel 认为它已执行过
测试环境用 migrate:fresh --seed,生产环境绝不能用
migrate:fresh 本质是 DROP DATABASE + CREATE DATABASE,哪怕只有一张表,也会清空全部数据。测试环境可以接受,生产环境一旦执行,就是事故。
线上真正安全的迭代方式是“增量迁移”:每次变更只新增一个迁移文件,用 up()/down() 控制单步增删改。
- 测试脚本里允许
migrate:fresh --seed,但必须限定数据库名(如DB_DATABASE=testdb)且不读取生产.env - 生产部署脚本中禁止出现
fresh、reset、reinstall类关键词 - 如果真需要重建结构(如大版本升级),应导出当前数据 → 手动执行新迁移 → 再导入,而不是依赖命令一键覆盖
关键点不在命令多难记,而在每条命令背后绑定的环境上下文是否被显式声明。很多人把本地写好的迁移一推就跑,却忘了 .env 里的 DB_CONNECTION 和 APP_ENV 才是真正的开关——它们不报错,但会让同一行 PHP 代码走完全不同的底层路径。


















