AI 协作数据库迁移——可回滚、有回填、能测

AI 在迁移上最常错的三件事:可回滚、回填策略、大表上的 NOT NULL。

把线上搞挂的迁移几乎从来不是显而易见危险那种。是那些在 dev 跑得绿油油的迁移——因为 dev 表里就 100 行。经典翻车现场:ALTER TABLE users ADD COLUMN locale TEXT NOT NULL DEFAULT 'en' 在一张 4000 万行的表上跑,周二午高峰把写锁了 18 分钟。AI 会高兴地原样写出这条迁移。下面这套流程让 AI 干它擅长的(写 up/down 对、生成回填脚本),同时把 AI 容易错的三件事——可回滚、回填策略、随规模放大的雷——握在你手里。

这篇主要解决什么问题

围绕”schema 变更 + 回填 + 可回滚”三角的迁移流程。怎么 prompt AI 生成 up/down 对、怎么在生产形态的克隆上验证、以及哪一类变更(大表上的 NOT NULL、还有读流量时丢列)你绝对不能信任 AI 生成的迁移不复核。

这篇适合谁看

每周发 schema 变更的后端工程师、用框架自动生成迁移的全栈(Prisma、Drizzle、Alembic、ActiveRecord)、负责共享数据库的平台团队、偶尔做一次迁移、做之间就忘掉那些坑的独立开发者。

什么时候适合用

加列或加表(最常见,多数安全)。给还有读流量的列改名(需要 expand/contract 模式)。丢列(要等读者停用之后)。从另一张表回填非规范化字段。把一张表拆成两张。10 万行以上、运行时长重要的表上的迁移。

什么时候不建议用

多区域复制拓扑变更——要 ops 评审。合规相关的数据搬动(GDPR 删除、审计日志)——每一行自己读。没克隆可以测的系统。“就在线上跑一条小迁移”——没有这种东西。

开工前

  • 准备生产形态的克隆。不一定全量数据——可以采样——但表的行数要对得上。1k 行 200ms 跑完的迁移,4000 万行可能跑 18 分钟。
  • 确认你的迁移框架既支持 up 也支持 down。有些团队禁了 down;如果是这样,把回滚步骤作为迁移 PR 的一部分写出来。
  • 配套的应用代码同时准备好。schema 迁移几乎从不单独发版——要配合读写新列的代码。提前规划发布顺序。
  • 分清哪些迁移需要维护窗口、哪些可以在线跑。加可空列通常在线。加 NOT NULL 或重写唯一约束通常不行。

三角

每条迁移都有三个属性。任一错就要付代价:

  1. Schema 变更 —— 实际的 DDL。AI 写得不错。
  2. 回填 —— 给新形态填数据。AI 能凑合写,但常忘了大表要分批。
  3. 可回滚 —— down 迁移。AI 能写出语法上的 down,但常写不出 正确 的 down(丢掉的列连数据带回不来)。

具体步骤

  1. 一句话写目标。“给 userslocale 列,默认 'en',给 4000 万存量行回填,发读它的代码。“含糊的”加多语言支持”会让范围爆炸。
  2. 让 AI 把 up/down 对和回填计划作为三个独立产物给你。不要让它合成一坨。
    git diff HEAD~1 -- migrations/  # 看 AI 生成了什么
  3. 逐行读 up 迁移。重点检查:对已有大表的 NOT NULL(红灯)、任何拿表级锁的 ALTER TABLE(红灯)、不带 CONCURRENTLY 的索引(Postgres 红灯)。
  4. 读 down 迁移。确认真能反掉 up。AI 有时会把 ADD COLUMN 的 down 写成 DROP COLUMN——回滚时把回填的数据一起销毁。如果数据从别处可恢复,可以;否则 down 要明确报错。
  5. 验证回填计划。10 万行以上的表,回填必须分批(比如每次 1 万行加 sleep)、可恢复。AI 倾向给你一条 UPDATE users SET locale = 'en' WHERE locale IS NULL——直接锁表。
  6. 在生产形态的克隆上跑完整迁移。计时。让应用对着迁移后的克隆跑。再跑 down。再跑一次 up。每步都计时。
  7. 规划发布:先发能兼容旧 schema 和新 schema 的代码,然后跑迁移,再发要求新 schema 的代码。expand / contract 模式。

能产出真实可用迁移的 Prompt

我需要一条 \{框架:Prisma / Drizzle / Alembic / 等\} 的迁移。

目标:\{一句——想要什么 schema 状态,为什么\}

现状:
- 表:\{名称\},线上约 \{N\} 行
- 现有 schema:\{粘相关 DDL\}

产出三个独立产物:

1. UP 迁移 —— DDL。标注任何拿表级锁或重写整表的语句。
   Postgres 索引优先 CONCURRENTLY。已有列上加 NOT NULL,拆成
   加可空、回填、再 SET NOT NULL。

2. DOWN 迁移 —— 必须真能反掉 1。回滚会丢回填数据的,down 应
   报错并给恢复步骤,而不是静默销毁数据。

3. 回填计划 —— 如果对超过 10 万行的现有表要回填,给出分批、可
   恢复的脚本(不是单条 UPDATE)。每批 1 万行。带进度日志。

不要合成一坨。不要生成 seed 数据。不要"顺便清理"迁移范围外的
内容。

质量检查

  • Up 迁移在大表上没有全表锁。已有大列上的 NOT NULL 拆成三步 expand 模式。
  • Down 迁移真能反掉 up。反掉会丢数据的,down 报错并给恢复步骤。
  • 10 万行以上表的回填分批且可恢复。大表单条回填一律拒掉。
  • 在生产形态克隆上端到端跑过迁移,含 down、再 up。墙钟时间记下来。
  • 要求新 schema 的应用代码在迁移 之后 发,不和迁移同一个 PR。expand / contract 顺序遵守。
  • 迁移 PR 描述里一行注:“预计锁时间:< 100ms” 或 “需要 5 分钟维护窗口。“

怎么把这流程沉淀下来

  • 保存三产物的 prompt。这是最大的一次性收益——分成 up / down / 回填三份比合并问一份安全得多。
  • 维护一份”迁移雷点 checklist”——大表 NOT NULL、不带 CONCURRENTLY 的索引、表级锁、还有读流量的丢列。每个 diff 都跑一遍。
  • 维护一份小表格”我们做过的迁移和克隆上耗时”。未来估时锚到真实数字,不靠猜。
  • 自动生成迁移的框架(Prisma、Drizzle),不要盲信生成的文件。先过一遍上面的 AI 评审 prompt 再 apply。

建议的操作流程

一句话目标 → AI 出 up/down/回填三产物 → 人逐个审锁风险和数据安全 → 在生产形态克隆上跑 → 全程计时 → 写发布顺序(兼容代码 → 迁移 → 要求代码)→ 发布。4000 万行表加一列,迁移 2 分钟,准备 30-60 分钟。比例对的。

容易踩的坑

  • 让 AI 给几百万行表写 ADD COLUMN ... NOT NULL DEFAULT 'foo'。整个迁移期间锁写。拆成加可空、回填、SET NOT NULL。
  • 信任自动生成的 down 迁移。尤其 ADD COLUMN,“显而易见”的 down 是 DROP COLUMN——把新增数据一起销毁。
  • 单条 UPDATE 做回填。大表必须分批、加 sleep。
  • 把要求新 schema 的代码和迁移塞在同一个 PR。要么迁移挂了代码也挂,要么迁移过了发布顺序很脆。永远两个 PR。
  • 在 100 行的 dev 上测,然后在 4000 万行的线上跑。锁竞争和耗时是非线性的。
  • 跳过 down 测试。从没跑过的 down 不工作。
  • 还有读的时候丢列。永远两阶段:停读、发布、再丢。

FAQ

  • 自动生成迁移的 ORM(Prisma、Drizzle)怎么办?: 拿 DDL 骨架可以;回填和锁风险不可信。apply 前一定要用上面的 prompt 复审。
  • 我的框架不支持 down 怎么办?: 那 “down” 就是附在 PR 上的书面恢复步骤。在克隆上测。
  • 还有读流量的时候必须丢列怎么办?: 两阶段。阶段一:停读、发布、观察残余读。阶段二:丢。永远不合并。
  • AI 能帮写回填脚本吗?: 能,前提是分批 / 可恢复 / 带进度日志这几条约束都给。不给约束,AI 会写一条锁表的单条 UPDATE。
  • 在线 schema 工具(gh-ost、pt-online-schema-change)怎么样?: AI 能帮写调用,但约束(必须有主键、副本延迟等)自己读工具文档。
  • 一次发布多条迁移怎么办?: 在克隆上按顺序跑通。迁移不一定可交换。把对的代码变更和对的迁移配对。

相关阅读

标签: #AI 编程 #工作流