跳到主要内容
Vabora 开发极客
首页 归档 分类 标签 案例 服务 关于 聊聊需求

数据库迁移的安全网:备份、预演与幂等

迁移脚本是唯一会直接改生产数据的代码。这篇讲三道安全网:执行前自动备份、--dry-run 预演、以及"可重复执行"的写法——顺便说说为什么回滚要慎用。

数据库迁移的安全网:备份、预演与幂等 封面

#背景

代码出问题可以回滚发布,数据出问题只能靠备份。而迁移脚本是唯一一类会直接改生产数据的代码——它却常常是团队里最没被认真审过的部分。

现实中的翻车方式很固定:加了 NOT NULL 列但表里已有数据、索引名与既有的冲突、迁移跑一半失败留下半截状态、down() 写错了还不如不写。

#三道安全网

#1. 执行前自动备份(默认行为,不是选项)

bash
php vabora migrate                # 自动先备份数据库
php vabora migrate --no-backup    # 明确跳过(本地开发用)

备份是动作级的:失败就中止迁移,而不是"备份失败了继续跑"。数据库小的时候备份只要几百毫秒,这笔时间不该省。

#2. 预演:先看清楚要跑什么

bash
php vabora migrate --dry-run      # 列出待执行迁移,不写库
php vabora migrate:status         # 已执行 / 未执行对照

预演要能回答三个问题:有几条、各自做什么、有没有已经存在的。看不到这些就点确认,等于闭眼提交。

#3. 幂等:让迁移"可重复执行"

迁移写法的第一原则不是 up/down 对称,而是重复执行不炸:

php
// 加列:先判断再执行(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() 比没有更危险——它给人虚假的安全感。
最后更新: 本文采用 CC BY-NC-SA 4.0 许可,转载请注明出处与作者。
related

相关文章

评论

还没有评论,来说点什么吧。

发表评论

评论需站长审核后展示 · 最多 1000 字