#背景
代码出问题可以回滚发布,数据出问题只能靠备份。而迁移脚本是唯一一类会直接改生产数据的代码——它却常常是团队里最没被认真审过的部分。
现实中的翻车方式很固定:加了 NOT NULL 列但表里已有数据、索引名与既有的冲突、迁移跑一半失败留下半截状态、down() 写错了还不如不写。
#三道安全网
#1. 执行前自动备份(默认行为,不是选项)
php vabora migrate # 自动先备份数据库
php vabora migrate --no-backup # 明确跳过(本地开发用)备份是动作级的:失败就中止迁移,而不是"备份失败了继续跑"。数据库小的时候备份只要几百毫秒,这笔时间不该省。
#2. 预演:先看清楚要跑什么
php vabora migrate --dry-run # 列出待执行迁移,不写库
php vabora migrate:status # 已执行 / 未执行对照预演要能回答三个问题:有几条、各自做什么、有没有已经存在的。看不到这些就点确认,等于闭眼提交。
#3. 幂等:让迁移"可重复执行"
迁移写法的第一原则不是 up/down 对称,而是重复执行不炸:
// 加列:先判断再执行(MySQL 老版本没有 ADD COLUMN IF NOT EXISTS)
if (!Schema::hasColumn('cases', 'deleted_at')) {
Schema::statement('ALTER TABLE `v_cases` ADD COLUMN `deleted_at` DATETIME NULL');
Schema::statement('CREATE INDEX `v_idx_cases_deleted_at` ON `v_cases` (`deleted_at`)');
}判断存在的价值在于:迁移记录表丢失、多人并行开发、从旧备份恢复后重跑——这些场景都会让"已经跑过的迁移"再跑一次。幂等让这些场景从事故变成无感。
#新增列的兼容顺序
字段变更最危险,顺序错了会有一段"新旧代码都不工作"的窗口:
1. 加列(可空,带默认值) → 旧代码不受影响
2. 部署写新列的新代码 → 新代码对旧数据用默认值
3. 回填历史数据(分批,可重跑) → 大表要分批,避免长事务锁表
4. 需要时再加约束(NOT NULL) → 此时数据已齐反过来(先加 NOT NULL 再部署代码)必然失败。先让数据就位,再收紧约束。
#回滚的边界
migrate:rollback 能回退最后一批,但要注意:
- 结构可回滚,数据不可回滚:删掉的列,回滚只能把列加回来,数据没了;
- 回滚不是"撤销部署":代码已经切到新版本,回滚数据库会让新代码找不到字段;
- 真正遇到问题时,正确的顺序是 先回滚代码,再决定要不要回滚结构。
所以:破坏性操作(删列、删表)要么分两次发布(先停用,观察一周再删),要么先备份再说服自己不会后退。
#结果
- 迁移从"手抖一下的事故源"变成常规操作:默认备份 + 预演 + 幂等,三条都做之后,本地与线上跑的是同一套命令;
- 一次真实的救场:恢复旧备份后重跑迁移,因为幂等写法,直接跑通而不需要手工删表。
#取舍
- 不自动执行线上迁移:部署流程里把迁移做成独立步骤(人工确认),而不是随代码自动跑。迁移失败与代码部署失败应该是两件可分开处理的事。
- 不为回滚写复杂
down():down()只做"能安全撤销的结构变更",涉及数据的一律标注"不可逆"。写一个假的down()比没有更危险——它给人虚假的安全感。
评论
还没有评论,来说点什么吧。