Retro会上,大家提出了两个痛点:

  1. Dev 环境数据库部署经常失败:多分支并行,合并后 Alembic 迁移时间戳冲突,数据库拒绝执行
  2. AI 协作效率低:每次让 AI 生成代码,都要先用 MCP 读取数据库结构,费 token 又慢

Alembic 的两个坑

之前用 Alembic 管理数据库迁移:写 Python 迁移脚本,按版本号顺序执行。用了一年多,问题越来越明显。

多分支迁移被跳过

团队多分支并行开发。两个人同时改表结构:

# 开发者 A
migrations/20250210100000_add_avatar.sql
 
# 开发者 B  
migrations/20250211090000_add_tags.sql

如果 B 先合并,A 后合并:

20250211090000_add_tags.sql     # ✅ 先执行
20250210100000_add_avatar.sql   # ❌ 被跳过(时间戳更早)

Alembic 检测到数据库已执行过 20250211...,拒绝执行更早的 20250210...。线上缺 avatar 字段,代码却在用,炸了。

AI 协作需要频繁读库

Alembic 没有统一 schema 文件,结构分散在几十个迁移脚本。每次让 AI 生成代码,都要先用 MCP 连数据库读表结构,等 2-3 秒,还消耗大量 token。

所以我们决定试试 Prisma。

Prisma 用 Schema 驱动

Prisma 的思路完全不同:Schema 驱动,不是迁移文件驱动

核心文件只有一个 schema.prisma

model User {
  id     String @id @default(cuid())
  email  String @unique
  avatar String?
  posts  Post[]
}
 
model Post {
  id       String @id
  title    String
  authorId String
  author   User   @relation(fields: [authorId], references: [id])
}

直接修改 schema,Prisma 自动生成迁移:

npx prisma migrate dev --name add-avatar
# 1. 对比 schema 和数据库,生成 SQL
# 2. 执行 SQL 更新数据库

迁移冲突变成代码冲突

两个分支同时改 schema.prisma,Git 合并时会提示冲突。手动解决后重新生成迁移,Prisma 自动生成包含两个改动的 SQL。冲突在代码层面解决,不会延迟到部署时才发现。

单文件包含所有上下文

schema.prisma 直接喂给 AI,一次性获取所有表结构。AI 响应速度提升 3 倍,token 消耗减少 60%,字段名错误率从 30% 降到 5%。

另外,Prisma 自动生成 TypeScript 类型定义,不用再手动维护三份代码(Alembic 迁移 + TypeScript 类型 + ORM 配置)。

怎么用

核心命令

命令场景
npx prisma migrate dev --name <描述>开发:修改 schema 后生成并应用迁移
npx prisma generate生成 TypeScript 类型定义
npx prisma migrate deploy生产:应用所有待执行的迁移
npx prisma migrate status检查数据库和 schema 是否同步
npx prisma db push快速原型:跳过迁移历史直接同步
npx prisma studio打开可视化数据库管理界面

客户端使用

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
 
// 类型安全的查询
await prisma.user.create({
  data: { email: 'user@example.com', posts: { create: [{ title: 'Post' }] } }
});
 
await prisma.post.findMany({ 
  where: { published: true }, 
  include: { author: true } 
});

三个命令的区别

Prisma 用 _prisma_migrations 表跟踪迁移历史。给 User 表加 avatar 字段为例:

model User {
  id     String @id
  email  String @unique
  avatar String?  // 新字段
}

migrate dev(开发环境)

npx prisma migrate dev --name add_avatar

生成迁移文件 → 执行 SQL → 写 _prisma_migrations 表。Prisma 用 migration_namechecksum 判断是否执行过,与时间戳无关。多分支合并后,只要 migration_name 不同,都会执行。

migrate deploy(生产环境)

npx prisma migrate deploy

检查 _prisma_migrations 表,找未执行的迁移,执行并写入记录。

db push(快速原型)

npx prisma db push

直接同步 schema 到数据库,不生成迁移文件,不写 _prisma_migrations 表。适合快速原型,生产环境慎用。

Prisma 也有限制

数据库触发器和函数

Prisma 不支持。需要手动编辑迁移文件追加 SQL:

-- migrations/xxx/migration.sql
ALTER TABLE "User" ADD COLUMN "avatar" TEXT;
 
-- 手动追加
CREATE TRIGGER update_timestamp 
BEFORE UPDATE ON "User" 
FOR EACH ROW EXECUTE FUNCTION update_updated_at();

复杂索引

// 简单索引可以
model User {
  email String @unique
  @@index([email, name])
}
-- 部分索引、表达式索引需手动添加
CREATE INDEX idx_active_users ON users(email) WHERE deleted_at IS NULL;
CREATE INDEX idx_lower_email ON users(LOWER(email));

数据库特定功能

  • PostgreSQL ENUM 修改(Prisma 会重建整个 ENUM)
  • 存储过程、视图、物化视图
  • 分区表、继承表

解决方案:手动编辑生成的迁移文件,追加自定义逻辑。

代码回滚后怎么办

部署新功能后发现 bug,紧急回滚代码。但数据库已执行了迁移,出现不一致:

  • 数据库_prisma_migrations 有记录,avatar 字段存在
  • 代码:迁移文件丢失(回滚到之前的 commit)

快速修复:标记迁移已应用

npx prisma migrate resolve --applied 20250213_add_avatar_field

告诉 Prisma “这个迁移已执行,虽然本地没文件,但不要再尝试”。系统继续运行,但数据库里多了个用不到的字段。适合数据库改动无害、不影响业务的情况。

开发/测试环境:重置数据库

npx prisma migrate reset  # 删除所有数据,重新执行迁移
npx prisma db seed         # 重新填充 seed 数据

适合可以接受数据丢失的环境。

生产环境:新增回滚迁移

npx prisma migrate dev --name remove_avatar

Prisma 生成删除字段的迁移:

-- migrations/20250214_remove_avatar/migration.sql
ALTER TABLE "User" DROP COLUMN "avatar";
npx prisma migrate deploy

保留完整迁移历史,可追溯,不丢失其他数据,可灰度发布。生产环境永远用新增迁移的方式”撤销”改动,保持迁移历史线性。

Prisma 完美解决了我们的问题:多分支合并不再有时间戳冲突导致部署失败,AI 直接读 schema.prisma 就能获取完整数据库上下文,不用再频繁调用 MCP 连库查表结构。当然,如果项目大量使用触发器、存储过程、物化视图这些数据库特定功能,还是老老实实用 Alembic。