自定义迁移、基线化与零停机数据迁移
本教程共 54 篇 · 第 32 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:学会编辑迁移 SQL 避免数据丢失,为已有数据库建立基线,并用 Expand-and-Contract 模式做到零停机改结构。
为什么要手改迁移
手改迁移有前提:你清楚每条 SQL 在干什么。改完之后要用 migrate dev 完整重放一遍验证,而不是只跑单条语句。验证通过后,这份改过的迁移文件就是标准历史,所有环境都会按它执行。
migrate dev 生成的 SQL 是按 Schema 差异机械推导的,有时不够聪明。典型例子:把字段 biograpy 改名为 biography。Schema 里删掉旧字段、加新字段,生成的 SQL 是:
ALTER TABLE "Profile" DROP COLUMN "biograpy",
ADD COLUMN "biography" TEXT NOT NULL;
这等于删掉旧列再建新列,数据全没了。我们想要的是:
ALTER TABLE "Profile"
RENAME COLUMN "biograpy" TO "biography";
流程分四步:
- 修改 Schema(改字段名)
npx prisma migrate dev --name rename-biography --create-only生成草稿迁移- 编辑
migration.sql,把 DROP+ADD 改成 RENAME - 再跑
npx prisma migrate dev应用
同理可以手改表重命名、加数据库扩展、写存储过程等 Prisma 表达不了的东西。
另一个常见场景是加数据库约束。Prisma 的 Schema 语言不支持 CHECK 约束,要加「金额必须大于 0」这类检查,只能手写进迁移:
ALTER TABLE "Order" ADD CONSTRAINT "Order_amount_check"
CHECK ("amount" >= 0);
流程一样:--create-only 生成草稿,把 CHECK 语句加进去,再应用。
一对一关系换方向(外键从 A 表挪到 B 表)也是典型场景:默认生成的 SQL 先删列再加列,数据跟着丢。手改时先加可空的新外键列,用 UPDATE 回填数据,再收紧为 NOT NULL,最后删旧列。
Note只有
--create-only生成的草稿迁移可以安全编辑。应用之后再改就是破坏历史,违反第 29 章的铁律。
基线化:给现有库补一份历史
接手一个已经上线、不能重置的数据库,直接 migrate dev 会试图创建已存在的表而失败。基线化(baselining)就是告诉 Prisma「这些结构早就存在,别重复建」。
步骤如下:
- 旧的
prisma/migrations/目录先归档或清空 - 用
migrate diff从空库到当前 Schema 生成基线迁移:
npx prisma migrate diff \
--from-empty \
--to-schema prisma/schema.prisma \
--script > prisma/migrations/0_init/migration.sql
- 标记它已应用:
npx prisma migrate resolve --applied 0_init
resolve --applied 只把迁移记入 _prisma_migrations 表,不执行任何 SQL。此后生产库的 migrate deploy 会跳过基线,只应用之后的新迁移;而新开发库会完整重放,从空库一路建到当前结构。目录名用 0_ 前缀,是为了让它排在历史最前面。
基线迁移标记为 applied 后,_prisma_migrations 表里会多一行记录,checksum 与实际文件一致。新同事克隆仓库跑 migrate dev 时,基线会被正常重放——开发库是空的,基线从零建起,正好补全历史。基线迁移只生成一次,之后新增的迁移都在它之上按时间戳排队。
Tip基线迁移里可以补上 Prisma 表达不了的东西——触发器、存储过程等。它本来就是给新库重放用的,多写不亏。
Expand-and-Contract:零停机改结构
生产环境改列名,最怕「迁移先改了结构,旧代码还在读旧列」。Expand-and-Contract(先扩后缩)把一步拆成四步,让新旧代码共存:
- 扩(Expand):加新列
biography,代码同时写两个字段、仍读旧字段,部署 - 复制数据:生成一个空迁移,填上回填 SQL,部署
UPDATE "Profile" SET biography = bio;
- 切换:代码改为读新字段、不再写旧字段,部署
- 缩(Contract):Schema 删掉旧字段
bio,生成迁移删列,部署
每步单独部署、单独验证,全程没有「结构已改但代码没跟上」的窗口。布尔转枚举、一对多转多对多、两个字段合并成一个,凡是涉及存量数据的改造都可以套这个模式。
代价是流程变长:一个改名要拆成三个迁移、三次部署。换来的是零停机,适合表大、流量高、不能接受维护窗口的系统。小项目、低流量阶段,直接改名加维护窗口往往更划算。
Note第 2 步的生成方式:先只加新列跑一次迁移,再
npx prisma migrate dev --name copy_biography --create-only生成空迁移,把UPDATE语句手写进去。
回滚策略:前向修复优于回滚
为什么没有自动回滚
迁移失败时,本能反应是「回滚」。但 Prisma 是前向工具:没有自动回滚,手动回滚要写反向迁移、处理数据一致性,代价很高。
想给每个迁移配一份反向 SQL(down migration),可以手工用 migrate diff 生成,但 Prisma 不会自动执行它们——第 34 章会讲怎么生成、何时使用。
前向修复的做法
推荐的顺序:先看能不能前向修复——补一个新迁移做反向操作,或者用 db execute 手工修正、resolve 对齐账本。只有迁移执行到一半、库处于半新半旧状态时,才考虑 resolve --rolled-back 回滚重来。
记住一个原则:数据库结构没有「撤销」,只有「下一个迁移」。把回滚想成「反向的新迁移」,心态就对了。
Tip大型迁移前先备份。测试环境用生产数据副本演练一遍,比在生产上赌运气靠谱得多。
参考来源
- Prisma 官方文档:Customizing migrations
- Prisma 官方文档:Baselining
- Prisma 官方文档:Data migration
- Generalist Programmer:Prisma Tutorial Complete Guide