3. Prisma Schema定义与迁移
Prisma 的核心工作方式可以概括为“先声明、再生成、后迁移”。schema.prisma 是整个数据访问层的唯一事实来源:它既描述数据库结构,也驱动 Prisma Client 的类型生成,还作为 Prisma Migrate 计算迁移差异的输入。理解这一节,关键不是记住语法,而是建立“Schema 声明 → 迁移 SQL → 数据库实际结构 → Client 类型”这条闭环链路。
一、Schema 的三段式结构与数据源声明
一个典型的 schema.prisma 由三部分组成:generator 定义客户端生成目标,datasource 定义数据库连接,model 定义数据模型。下面是一个教学示例,假设我们为一个小型内容管理系统建模:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
tags Tag[]
createdAt DateTime @default(now())
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
}
这里 provider = "postgresql" 决定了迁移生成的 SQL 方言;url = env("DATABASE_URL") 表示连接串从环境变量读取,避免把凭据写进版本库。切换数据库时,provider 与连接串需要同步调整,否则迁移 SQL 会与目标数据库不兼容。
二、字段类型与属性修饰符
Prisma 的标量类型包括 String、Int、BigInt、Float、Decimal、Boolean、DateTime、Json、Bytes。其中 Decimal 适合金额等需要精确计算的场景,Float 是浮点数,不适合直接用于财务累加。类型后的 ? 表示可空,例如 name String? 对应数据库中的 NULL 允许。
属性修饰符决定字段在数据库层面的约束与行为,常用的有:
@id:主键;@default(autoincrement())让数据库自增生成。@unique:唯一约束,如email、Tag.name。@default(...):默认值,可写常量、now()、uuid()或枚举值。@updatedAt:Prisma Client 在更新记录时自动写入当前时间。@map("..."):把字段映射到数据库中不同命名的列,常用于适配既有表结构。@@index([...])与@@unique([...]):模型级复合索引与复合唯一约束。
需要注意,@updatedAt 由 Prisma Client 维护,而不是数据库触发器;如果绕过 Prisma 直接写库,该字段不会自动更新。这是排查“更新时间不变化”问题的常见切入点。
三、关系声明:一对多与多对多
Prisma 的关系通过两侧字段共同表达。以 User 与 Post 为例,Post.authorId 是外键标量字段,Post.author 是关系字段,@relation(fields: [authorId], references: [id]) 明确外键指向 User.id;User.posts Post[] 是反向关系。两侧必须成对出现,否则 prisma validate 会报错。
多对多关系在 Prisma 中通常写成两侧数组,例如 Post.tags Tag[] 与 Tag.posts Post[]。Prisma Migrate 会隐式创建一张连接表(如 _PostToTag),包含两个外键列并建立复合唯一索引。如果需要给关系附加额外字段(例如“打标签时间”),就必须显式定义连接模型,把隐式多对多改为“一对多 + 一对多”的组合。
四、Prisma Migrate 工作流
开发阶段的标准流程是:修改 schema.prisma → 执行 npx prisma migrate dev --name add_tag → Prisma 对比当前数据库结构与 Schema,生成新的迁移 SQL 并应用到开发库,同时触发 Client 重新生成。迁移文件一旦生成就应提交到版本库,它是团队协作与生产部署的依据。
# 开发环境:生成并应用迁移
npx prisma migrate dev --name add_tag
# 生产环境:只应用已有迁移,不生成新迁移
npx prisma migrate deploy
# 查看迁移状态与差异
npx prisma migrate status
# 校验 Schema 语法与关系完整性
npx prisma validate
生产环境必须使用 migrate deploy,它只按顺序执行 migrations 目录中尚未应用的迁移,不会尝试修改 Schema 或重置数据库。若生产库结构与迁移历史不一致,migrate status 会提示存在未应用的迁移或漂移,此时应先排查,而不是直接执行 migrate reset——后者会清空数据库,仅适用于本地开发。
五、数据库版本管理与漂移处理
Prisma 在数据库中维护一张 _prisma_migrations 表,记录每个迁移的名称、校验和、开始与结束时间。当有人手动改了数据库结构(例如直接 ALTER TABLE),迁移历史与真实结构就会产生“漂移”。处理漂移的常规做法是:先用 npx prisma migrate diff 对比数据库与 Schema 的差异,确认差异内容后,要么生成一个补充迁移把手工改动纳入版本管理,要么在开发环境用 migrate reset 重建并重新应用全部迁移。
prisma migrate reset 或 db push。db push 适合原型阶段快速同步结构,但它不生成迁移文件,无法形成可审计的版本历史。npx prisma migrate dev 让本地库追上,再继续开发。若多人同时修改同一模型,容易产生迁移冲突,应尽早合并并重新生成迁移。把这条链路串起来看:Schema 决定迁移内容,迁移决定数据库结构,数据库结构又决定 Client 能安全访问哪些字段。任何一环脱节,都会表现为运行时报错、类型不匹配或数据不一致。因此,每次改模型后,建议按“validate → migrate dev → generate → 运行查询验证”的顺序走一遍,确保声明、结构与代码三者始终一致。