把线上搞挂的迁移几乎从来不是显而易见危险那种。是那些在 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 或重写唯一约束通常不行。
三角
每条迁移都有三个属性。任一错就要付代价:
- Schema 变更 —— 实际的 DDL。AI 写得不错。
- 回填 —— 给新形态填数据。AI 能凑合写,但常忘了大表要分批。
- 可回滚 —— down 迁移。AI 能写出语法上的 down,但常写不出 正确 的 down(丢掉的列连数据带回不来)。
具体步骤
- 一句话写目标。“给
users加locale列,默认'en',给 4000 万存量行回填,发读它的代码。“含糊的”加多语言支持”会让范围爆炸。 - 让 AI 把 up/down 对和回填计划作为三个独立产物给你。不要让它合成一坨。
git diff HEAD~1 -- migrations/ # 看 AI 生成了什么 - 逐行读 up 迁移。重点检查:对已有大表的
NOT NULL(红灯)、任何拿表级锁的ALTER TABLE(红灯)、不带CONCURRENTLY的索引(Postgres 红灯)。 - 读 down 迁移。确认真能反掉 up。AI 有时会把
ADD COLUMN的 down 写成DROP COLUMN——回滚时把回填的数据一起销毁。如果数据从别处可恢复,可以;否则 down 要明确报错。 - 验证回填计划。10 万行以上的表,回填必须分批(比如每次 1 万行加 sleep)、可恢复。AI 倾向给你一条
UPDATE users SET locale = 'en' WHERE locale IS NULL——直接锁表。 - 在生产形态的克隆上跑完整迁移。计时。让应用对着迁移后的克隆跑。再跑 down。再跑一次 up。每步都计时。
- 规划发布:先发能兼容旧 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 能帮写调用,但约束(必须有主键、副本延迟等)自己读工具文档。
- 一次发布多条迁移怎么办?: 在克隆上按顺序跑通。迁移不一定可交换。把对的代码变更和对的迁移配对。