数据库迁移规则写进技术规格:变更与回滚策略

数据库迁移规则写进技术规格:变更与回滚策略
Spec Coding 编辑部 · Spec-First 工程实践内容

我见过的大多数生产事故,都来自那种评审时看着挺合理、却在周二下午两点把数据库干崩的迁移。解决办法是一份技术规格,它强制你在写下第一条 ALTER TABLE 之前,就对扩展-收缩、回滚、部署顺序这些问题给出具体答案。

发布于 2026-03-01 · ✓ 已更新 2026-05-06 · 阅读约 7 分钟 · 作者:Spec Coding 编辑部 · 审校:编辑与事实核查政策

现场笔记:写 SQL 前我想先看到的一行

迁移脚本出现之前,我想先看到一句回滚限制。有些迁移可以机械回滚,有些一旦数据转换完成,就只能向前修复。这个区别应该写进规格。

迁移回滚说明:
- Expand 阶段:可通过删除 nullable column 回滚。
- Backfill 阶段:行数据转换后无法完全回滚。
- Contract 阶段:旧代码保持兼容 7 天。
- 停止触发:连续 3 次检查 lock wait 超过 5 秒。

每份迁移规格都必须声明阶段模型

我的默认规则是:只要一列、一张表或一个约束正在被线上代码使用,这次迁移就不能作为单个步骤上线。规格必须显式写出阶段模型:阶段 1 新增结构,阶段 2 双写或回填,阶段 3 移除旧结构。这就是扩展-收缩模式,有时也叫 parallel-change,任何想把它压缩成一次部署的规格我都会直接打回去。

一个具体例子:上个季度我们把 users.email 重命名为 users.email_address。最初的规格只有一次迁移加一次代码变更。最终上线的规格变成九天里的三次发布。阶段 1 新增列并加了一个触发器来复制写入。阶段 2 把读切到新列上(加 feature flag 保护),并回填历史数据。阶段 3 删除触发器、旧列和 flag。每个阶段都可以独立回滚。

向后兼容窗口必须以"天"为单位写明

一份写着"旧代码会继续工作"的迁移规格不是规格,是许愿。我要求每份规格都必须说明旧应用代码需要与新 schema 保持兼容多少天,反向也一样。这个答案几乎从来都不是零。

下限是一个完整的部署周期加一个回滚窗口。如果部署要一小时、回滚保留 24 小时,窗口至少是 25 小时。如果灰度要跑一周,那窗口就是一周。规格把这个数字写出来,评审人才能检查阶段方案有没有违反它。谁要是敢在代码刚停止写入某列的当天就 drop 掉它,这份规格会被我退回去。

在线 DDL 是一个决策,不是默认项

规格必须逐条告诉我,这条 DDL 在线上是不是安全。在 Postgres 上,新增一个可空列几乎没有代价;但给一张 5000 万行的表新增一个带非常量默认值的 NOT NULL 列,会在 ACCESS EXCLUSIVE 锁下重写整张表,把站点直接搞挂。规格必须把这两种情况区分开。

大表必然意味着分批回填

一旦表的体量超过大约一百万行,规格就必须描述回填策略,不是只提一句"有回填"。我要看批大小、游标分页键、预期耗时、每批的锁行为,以及作业在崩溃后是否可恢复。

最近一份规格里的例子:5000 万行的 orders 回填,用 id 游标分页,每批 5000 行,每批之间 sleep 100ms,用一张 checkpoint 表记录最后处理到的 id,还有一个从 Redis 读取的 kill switch。预期耗时 14 小时。规格里写明副本延迟阈值(10 秒)会触发作业自动暂停。没有这些,回填一撞上副本延迟,总会有人在凌晨三点手动把它停掉。

回滚方案就是规格本身,不是附录

如果回滚方案只有一句"revert the migration",我会拒掉这份迁移规格。回滚是第一公民。规格要逐阶段告诉我:revert 意味着什么、以新形态写入的数据能否恢复、以及在下一阶段让回滚变得不可逆之前,这个回滚窗口还能用多久。

有些迁移一旦写入新数据就真的不可逆:丢精度的类型变更、会删行的去重、会覆盖旧数据的反范式化。规格必须写明这一点,并标出"不可回头的那个点"。对这类迁移,我要求在不可逆步骤之前立即拍一次快照,并且恢复流程已经在 staging 副本上演练过。

部署顺序依赖是一级章节

我排查过的迁移事故里,有一半来自部署顺序搞反。规格必须回答:代码是在迁移之前部署、之后部署,还是在不同阶段里两者都有?对扩展-收缩而言答案永远是"两者都有",但规格要逐次列出每一次部署,并分别考虑其失败模式。如果代码先上、迁移挂了,会崩什么?如果迁移先上、代码发布卡住了,会崩什么?这两个问题都没答案,这份方案就不能算准备好。

验收标准使用 Given/When/Then

我希望迁移的验收标准用跟功能特性一样的 Given/When/Then 格式来写,因为它们能用同样的方法被测试。

- Given the phase 1 migration has run on staging with a prod-sized snapshot
  When the existing application code is deployed against the new schema
  Then all existing read and write paths succeed with zero error-rate delta

- Given the backfill job has processed all rows
  When we run the verification query comparing old and new columns
  Then zero mismatched rows are returned and the result is logged to the release ticket

- Given we are inside the backward compatibility window
  When we execute the rollback procedure for the current phase
  Then the application returns to the prior schema state within 10 minutes with no data loss

测试需要生产规模的数据和演练

只在空的 staging 数据库上跑过的迁移,不叫测试过。我的规则是:每份规格都要写明演练用的数据集,并且这个数据集在受影响表上的行数必须与生产在同一数量级。演练要跑两遍:一遍测耗时和锁行为,一遍执行文档里写好的回滚。两次都要打时间戳并挂到发布工单上。如果回滚演练被跳过了,不管正向迁移跑了多少次,这份规格都算没测过。

会被评审直接打回的红旗

下面这些模式,不改我不批:

这些都不是个人审美偏好。每一条背后都对应一份我自己写过的故障复盘。规格是把它们拦下来最便宜的地方。

评审时看什么

这篇文章适合用在数据库迁移规格评审时。别从“原则”聊起,直接拿一条真实改动来对照,看看规格里还缺什么。

迁移规格不是字段说明,而是风险说明。评审时最该问的是:哪一步还能停,哪一步之后只能补救。

迁移规格里必须有 dry-run 证据

数据库迁移最不该靠“应该没事”。规格要要求 dry-run 输出:影响多少行、锁多久、是否走索引、失败后如何恢复。没有这几项,reviewer 只能凭经验猜。

Migration evidence:
- table: invoices, rows=18,420,331
- operation: add nullable column refund_status
- lock check: metadata-only in Postgres 15
- backfill: 10,000 rows per batch, sleep 200ms
- dry-run: staging copy completed in 14m32s
- rollback: stop worker, keep nullable column, ignore field in serializer

边界:小表不等于低风险。认证、计费、权限和审计表即使只有几万行,也要按高风险迁移审。

可复制产物:规格写作片段

当工单看似清楚但还缺验收语言时使用。它要求作者写出角色、触发、结果和证据。

规格写作评审片段:数据库迁移规则写进技术规格:变更与回滚策略

本次要做的决策:
- 把模糊需求改写成可以由工程和 QA 共同判断的验收条款。

责任人检查:
- 产品责任人:
- 工程责任人:
- QA 或运维评审:

范围边界:
- 本次包含:
- 本次不包含:
- 仍需确认的假设:

验收证据:
- 测试或 fixture:
- 日志、指标或截图:
- 人工复核步骤:

写作边界:避免模糊动词,每条验收标准都要有可见的通过或失败信号。

评审追问:
- 没参加需求会的人还会误解哪里?
- 哪个证据能证明这次改动足够安全,可以发布?

旗舰使用路径

这是 Spec Coding 用来承接「数据库迁移规格」主题的核心参考页之一。建议把它放到真实工单、PR 或发布评审里使用,而不是只当背景文章阅读。

旗舰页使用路径:
- 在计划或评审时打开本文。
- 把对应产物复制到工单或 PR。
- 用自己的系统、责任人和失败模式替换示例值。
- 如果证据行仍为空,就不要进入实现。

二次审阅记录:数据安全需要时间信息

这次检查文章是否写明时间、锁风险和可逆性。迁移建议一旦忽略运行多久、哪个阶段不可撤销,就会变泛。

迁移评审:
- 估算表大小和 backfill 时长。
- 写明最长预期锁等待。
- 拆分 expand、backfill、contract。
- 明确什么时候回滚不再现实。

编辑复核记录

复核日期:2026-04-29。本次补充了可复用产物,按相关主题 Hub 检查了文章定位,并收紧下一步链接,让页面更像可操作参考,而不是孤立长文。

关键词:expand-contract migration · online DDL · backward compatibility window · migration rollback plan · batched backfill · gh-ost · pt-online-schema-change

专题阅读路径

这篇文章归入 AI 编码治理 主题。先读 Hub,再结合下面的清单、模板或工具落到具体项目里。

编辑说明与免责声明

最近复核:2026-04-29。编辑部检查了示例、内链和可复制评审片段,确保内容更适合真实项目使用。

本文用于软件工程教学与实践参考,不构成法律、税务或投资建议。示例场景用于解释规格方法,不对应真实客户数据。