第 3 篇:Prisma Schema 全面拆解

在第 2 篇里,我们已经用 Prisma 7 + Next.js 16.2.2 + PostgreSQL + pnpm 搭好了一个项目,并且在 prisma/schema.prisma 中定义了一个最小可用的 User 模型。
这一篇的目标是:把这份 schema 从上到下拆开讲清楚——datasource、generator、model、enum、各种属性都是什么、怎么用。看完之后,你应该能:
自己读懂 / 修改现在项目里的
schema.prisma;按业务需求扩展
User模型;为后面关系建模、迁移策略打好语法基础。
1. 回顾一下我们当前的 schema.prisma
先看一下目前项目里的 prisma/schema.prisma(第 2 篇的版本):
generator client {
provider = "prisma-client"
output = "../generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
}
Prisma 官方文档会告诉你:Prisma Schema 一共就三块:
datasource:数据源配置;
generator:生成器配置;
data model:数据模型定义(model、enum 等)。
这一篇就是沿着这三个部分展开。
2. datasource:告诉 Prisma “去哪儿连库”
2.1 我们当前的 datasource 长什么样?
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
这段配置的作用是:
名字叫
db(可以改,但没必要);provider = "postgresql":告诉 Prisma 使用 PostgreSQL 连接器;url = env("DATABASE_URL"):从环境变量读取连接串。
结合第 2 篇 .env 的写法:
DATABASE_URL="postgresql://prisma:prisma@localhost:5432/next_prisma_demo?schema=public"
完整含义就是:
协议:
postgresql://;用户/密码:
prisma:prisma;host / port:
localhost:5432;数据库名:
next_prisma_demo;schema:
public(Postgres 的 schema 概念,不是 Prisma Schema 文件)。
2.2 datasource 还能改哪些东西?
在 Prisma Schema Reference 里,datasource 除了 provider 和 url,还有一些更高级的配置,例如 relations 模式等,不过你目前用 Postgres,常见配置基本只用这两个就够了。
此时你要记住的一句话是:
Prisma 只通过 datasource 知道“你在连什么数据库”,并据此决定支持哪些类型、特性。
3. generator:告诉 Prisma “生成什么客户端”
3.1 Prisma 7 推荐的 generator 写法
在 Prisma 7 中,我们在 schema 里写的是:
generator client {
provider = "prisma-client"
output = "../generated/prisma"
}
官方文档说明:对于 prisma-client generator,output 是必填,用来告诉 Prisma 把生成的 Client 放到哪里。
配合第 2 篇的项目结构,这代表:
schema.prisma在prisma/;output = "../generated/prisma";所以 Client 会生成到项目根目录的
generated/prisma目录里。
这就是我们在 lib/prisma.ts 里这样导入的原因:
import { PrismaClient } from '../generated/prisma'
3.2 generator 的几个关键点
基于 Prisma 7 官方文档,generator 有这些要点值得记:
provider:"prisma-client":生成 Prisma Client(现代、ESM-first 的方式);output:必填;
决定生成的 Client 代码路径;
用相对 schema 的路径,例如
../generated/prisma。
如果你以后在 monorepo / 多包结构中使用 Prisma,output 会更重要;当前这个单包项目,用默认的 generated/prisma 路径已经足够清晰。
4. data model:User 模型里的每一部分
现在进入最重要部分:数据模型。官方文档称这一部分为 Data Model Definition,包括 model、enum、attributes 等。
我们当前的 User 模型是:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
}
可以先把它拆成几个维度来理解:
每一行是一个字段;
每个字段有“名字 + 类型 + 可选修饰符 + 属性”;
类型可以是标量(String、Int 等)或关系(比如后面会有
posts Post[])。
我们先只看“标量 + 基本属性”,关系下一篇专讲。
4.1 字段结构:名字 + 类型 + 可选修饰
以 email 行为例:
email String @unique
拆开就是:
字段名:
email(在 Prisma Client 中就是user.email);字段类型:
String;属性:
@unique(字段级唯一约束)。
再看 id 行:
id Int @id @default(autoincrement())
名字:
id;类型:
Int;属性:
@id表示主键;@default(autoincrement()):默认值来自自增序列。
这正好印证了官方对 model 字段的定义:字段 = 名字 + 类型 + 可选修饰符(?、[])+ 属性。
4.2 标量类型(Scalar Types)
Prisma 支持一组标量类型,官方文档列了常用这些:
StringBooleanIntBigIntFloatDecimalDateTimeJsonBytes
以及一些 provider-specific 的原生类型映射(通过 @db.* 属性),比如 @db.VarChar(255)、@db.Decimal(10,2) 等。
如果我们要把 User 模型稍微扩展一下,例如加一个余额字段,可以写成:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
balance Decimal @default(0)
createdAt DateTime @default(now())
}
具体映射到 Postgres 时,可以再加原生类型属性,例如:
balance Decimal @db.Decimal(10, 2) @default(0)
这样 Prisma 就知道应该在数据库里用 DECIMAL(10,2)。
4.3 可空与数组
Prisma 中类型后面的 ? 和 [] 是两个非常常用的修饰符:
?:表示该字段可以为 NULL;[]:表示这是一个数组类型(或关系数组)。
比如我们想让 name 变成可选字段:
name String?
或者给 User 加一个字符串数组字段 tags:
tags String[]
目前我们的 User 是故意保持简单:所有字段都必填,暂不使用数组;后面做关系建模时,你会看到类似 posts Post[] 这样的数组类型字段。
5. 字段属性:用 @xxx 给字段加“约束和语义”
Prisma 用所谓的“属性(Attribute)”来描述字段的约束和语义,写在字段后面,以 @ 开头。
在 User 里我们已经用到了这些:
@id@default(...)@unique[]
5.1 @id:主键
id Int @id @default(autoincrement())
Prisma 文档说明:ID 字段唯一标识模型中的每条记录,一个 model 可以有一个 @id 或一个 @@id(复合主键)。
我们这里使用的是最常见的“单字段整数主键 + 自增”。
5.2 @default(...):默认值
@default() 可以给字段指定默认值或默认函数。常见组合:
整数自增:
@default(autoincrement())当前时间:
@default(now())固定值:
@default("guest")、@default(false)等;UUID:
@default(uuid())。
在 User 中,我们用的是:
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
对应数据库层面就是:id 用序列/自增,createdAt 默认填入插入时的时间。
5.3 @unique:唯一约束
email String @unique
表示 User.email 在数据库中必须唯一。Prisma Migrate 会为此创建一个唯一索引。
你以后在业务里尝试插入重复 email 时,会收到类似 “P2002 Unique constraint failed” 的错误,这可以用来做用户注册冲突校验。
5.4 @updatedAt:自动更新时间(可选补充)
虽然当前 User 模型里还没用,但你基本一定会用到的另一个常见属性是 @updatedAt:
updatedAt DateTime @updatedAt
Prisma 每次更新这条记录时,会自动把
updatedAt写成当前时间;不需要你在代码里手动赋值。
你可以考虑在当前项目里把 User 模型升级为:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
6. 模型属性(@@xxx):在模型层面加规则
模型属性写在 model 块的底部,不属于任何字段,以 @@ 开头,常见的有:
@@map("table_name"):映射表名;@@unique([field1, field2]):复合唯一;@@index([field1, field2]):索引;@@id([field1, field2]):复合主键。
6.1 @@map:自定义表名
目前不一定用得上,但理解一下很有用。比如你想在代码里叫 User,数据库里表名叫 users:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
@@map("users")
}
这样:
Prisma Client 中照样用
prisma.user.findMany();数据库里实际操作的是
users表。
适合“老数据库接入 + 新代码”场景。
6.2 @@index:为常用查询加索引
假设你经常按 createdAt 查询最新用户,可以加一个索引:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
@@index([createdAt])
}
或者你有一个组合查询需求,比如常按 (email, createdAt) 组合过滤,也可以这样写:
@@index([email, createdAt])
Prisma Migrate 会生成对应的索引 SQL,提高查询性能。
7. enum:固定值集合的更好选择(预备给 User 增强)
当字段取值是一个有限集合,比如用户角色、订单状态、任务优先级等,不建议用随意字符串,官方推荐用 enum。
7.1 为 User 增加 Role 枚举字段
我们可以在当前 schema 上加一个简单枚举:
enum Role {
USER
ADMIN
}
然后在 User 模型中增加一个 role 字段:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
好处是:
在 schema 层面锁死取值,只能是
USER或ADMIN;Prisma Client 的 TS 类型也会反映这个枚举;
数据库层面也会用枚举/受限类型来保证合法性(取决于 provider)。
在代码里会变成类似这样的体验:
const user = await prisma.user.create({
data: {
name: 'Alice',
email: 'alice@example.com',
role: 'ADMIN', // 有 TS 类型提示
},
})
Prisma 官方 Schema 文档建议:凡是“固定集合”的字符串字段,能用 enum 就用 enum,这可以减少拼写错误和魔法字符串。
8. 关于关系(@relation):在下一篇用博客系统专讲
在第 2 篇和本篇中,我们刻意只用了单表 User 模型,目的是把 Schema 语言的基础部分讲清楚。关系建模(User–Post–Comment 这种)会在下一篇里用“博客系统”的完整例子集中拆解。
这里先给一个概念图,和官方文档里的模型类似:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
}
你可以先记住这些点即可:
关系字段:例如
posts Post[],类型是 另一个 model,而不是标量;外键字段:例如
authorId Int;@relation(fields: [...], references: [...]):指明外键关系。
下一篇会专门围绕“博客系统 User / Post / Comment”来讲一对一、一对多、多对多和嵌套写入等内容。
9. 和当前项目的实践连接:你现在能做什么?
结合第 2 篇和本篇,你现在已经可以对当前项目的 schema.prisma 做一些稳当的增强,例如:
给
User加updatedAt字段:updatedAt DateTime @updatedAt定义
Role枚举并为User增加role字段;为常用查询字段加索引,例如
createdAt;用
@@map兼容已有数据库中的表名规则(如果你接老库)。
每次修改 schema 后,你都可以:
跑
pnpm prisma migrate dev --name something生成迁移并同步本地库;使用
pnpm prisma studio打开 UI 看看字段和数据是否如预期;在 Next.js 里通过
prisma.user.findMany()等查询新字段。
当你之后把 Prisma MCP 接进来时,AI 大部分也就是帮你改这个 schema 和跑这些命令,所以理解 schema 的语义,是你能“审查 AI 输出”的前提。
10. 小结:这一篇你需要真正掌握的点
快速过一下,如果满足这些,说明你已经掌握了 Prisma Schema 的基础语法:
看得懂现在
schema.prisma里的三块:generator/datasource/model User;知道
generator client在 Prisma 7 中用provider = "prisma-client" + output配置;知道 datasource 的
provider = "postgresql"和url = env("DATABASE_URL")分别含义是什么;能说出
User模型每个字段的类型、可空性和属性语义(@id、@default、@unique、@updatedAt);大致知道 enum 在什么场景下比普通字符串更合适(角色、状态等固定集合);
能用
@@index和@@map为模型添加索引和表名映射。