首页 / Prisma ORM 入门教程 / 自定义迁移、基线化与零停机数据迁移

Prisma ORM 入门教程

自定义迁移、基线化与零停机数据迁移

本教程共 54 篇 · 第 32 篇 · 更新于 2026-08-11 · 约 6 分钟阅读

Prisma迁移自定义迁移基线Expand-and-Contract零停机数据迁移

本节目标:学会编辑迁移 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";

流程分四步:

  1. 修改 Schema(改字段名)
  2. npx prisma migrate dev --name rename-biography --create-only 生成草稿迁移
  3. 编辑 migration.sql,把 DROP+ADD 改成 RENAME
  4. 再跑 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「这些结构早就存在,别重复建」。

步骤如下:

  1. 旧的 prisma/migrations/ 目录先归档或清空
  2. migrate diff 从空库到当前 Schema 生成基线迁移:
npx prisma migrate diff \
  --from-empty \
  --to-schema prisma/schema.prisma \
  --script > prisma/migrations/0_init/migration.sql
  1. 标记它已应用:
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(先扩后缩)把一步拆成四步,让新旧代码共存:

  1. 扩(Expand):加新列 biography,代码同时写两个字段、仍读旧字段,部署
  2. 复制数据:生成一个空迁移,填上回填 SQL,部署
UPDATE "Profile" SET biography = bio;
  1. 切换:代码改为读新字段、不再写旧字段,部署
  2. 缩(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