外观
Prisma ORM 系统学习文档
适合人群:已经掌握 Node.js、TypeScript、基础 SQL,准备在 NestJS、Express、Next.js、Nuxt Server、全栈项目中使用 ORM 管理数据库的学习者。
学习目标:从 ORM 的基本概念开始,系统掌握 Prisma Schema、Prisma Client、Prisma Migrate、关系建模、事务、原生 SQL、生产部署和常见性能问题。
核对时间:2026-06-25。本文以 Prisma ORM 官方文档当前信息为基础整理,重点参考 Prisma Client、Prisma Schema、Prisma Migrate、Raw SQL 和 Relations 文档。
官方资料:
- Prisma ORM 文档:https://www.prisma.io/docs/orm
- Prisma Client:https://www.prisma.io/docs/orm/prisma-client
- Prisma Migrate:https://www.prisma.io/docs/orm/prisma-migrate
- Prisma Relations:https://www.prisma.io/docs/orm/prisma-schema/data-model/relations
- Prisma Raw SQL:https://www.prisma.io/docs/orm/prisma-client/using-raw-sql
目录
第一篇:基础认知
- Prisma ORM 是什么
- ORM 解决什么问题
- Prisma 和传统 ORM 的区别
- Prisma 的核心组成
- Prisma 适合什么项目
第二篇:快速入门
- 安装与初始化
- schema.prisma 文件结构
- datasource、generator、model
- 第一次迁移
- 生成 Prisma Client
第三篇:数据模型
- 字段类型
- 主键和唯一约束
- 默认值和更新时间
- 枚举 enum
- 一对一、一对多、多对多
- 映射数据库表名和字段名
第四篇:Prisma Client 查询
- 查询单条和多条
- 创建、更新、删除
- 条件过滤
- 排序、分页和游标
- include 与 select
- 聚合、分组和计数
第五篇:迁移、事务与生产实践
- Prisma Migrate 工作流
- 开发迁移和生产迁移
- 事务
- 原生 SQL
- 性能优化
- 与 PostgreSQL/MySQL 的关系
- 常见误区和学习路线
第一篇:基础认知
1. Prisma ORM 是什么
Prisma ORM 是一个面向 TypeScript/JavaScript 的数据库访问工具。它让你用类型安全的方式操作数据库,而不是在代码中到处手写 SQL 字符串。
典型写法:
ts
const user = await prisma.user.findUnique({
where: { id: 1 },
});如果使用原生 SQL,类似这样:
ts
const result = await pool.query(
"select * from users where id = $1",
[1]
);Prisma 的价值在于:
- 根据 schema 自动生成类型安全 Client。
- 写 CRUD 更快,减少手写 SQL 错误。
- schema 和迁移文件可以版本化。
- 查询结果有 TypeScript 类型提示。
- 关系查询比手写 join 更容易上手。
但要记住:Prisma 不是数据库本身,也不是 SQL 的替代品。它是应用层访问数据库的工具。真正的数据一致性、事务、索引和查询性能仍然由 PostgreSQL、MySQL 等数据库决定。
2. ORM 解决什么问题
ORM 是 Object Relational Mapping,对象关系映射。
数据库中的表:
sql
users
- id
- email
- name应用中的对象:
ts
type User = {
id: number;
email: string;
name: string;
};ORM 负责在“表”和“对象”之间建立映射。这样应用代码可以更接近业务对象,而不是每次都拼 SQL。
ORM 主要解决:
- 类型提示
- 重复 CRUD
- 关系加载
- 迁移管理
- 参数化查询
- 数据模型集中管理
ORM 不擅长:
- 极复杂报表 SQL
- 数据库特定高级优化
- 替你设计索引
- 替你理解事务隔离
- 替你判断表结构是否合理
3. Prisma 和传统 ORM 的区别
一些传统 ORM 常见模式是“实体类 + 装饰器”:
ts
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
email: string;
}Prisma 的中心是 schema.prisma:
prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String
}Prisma 根据 schema 生成 Client:
ts
await prisma.user.findMany();这种模式的特点:
- 数据模型集中在 schema 文件中。
- Client 类型由生成器自动生成。
- 查询 API 很接近 JSON 对象。
- 迁移和 schema 关系更明确。
4. Prisma 的核心组成
Prisma ORM 常见组成:
text
schema.prisma
├── datasource:数据库连接和类型
├── generator:生成 Prisma Client
└── model:数据模型
Prisma Migrate
└── 根据 schema 生成 SQL 迁移文件
Prisma Client
└── 类型安全的查询客户端
Prisma Studio
└── 可视化查看和编辑数据常用命令:
bash
npx prisma init
npx prisma migrate dev
npx prisma generate
npx prisma studio
npx prisma migrate deploy5. Prisma 适合什么项目
适合:
- TypeScript 后端项目
- NestJS、Express、Fastify
- Next.js、Nuxt 全栈项目
- CRUD 较多的业务系统
- 需要快速开发原型和后台系统
- 团队希望统一 schema 和迁移流程
不太适合:
- 极度依赖复杂 SQL 报表的项目
- 对 SQL 可控性要求极高的数据库平台
- 团队完全不了解数据库基础
- 需要高度自定义底层查询计划的核心系统
最理想的学习方式:先学 PostgreSQL/MySQL 基础,再学 Prisma。这样你知道 Prisma 生成的操作最终会落到什么数据库行为上。
第二篇:快速入门
6. 安装与初始化
安装:
bash
npm install prisma --save-dev
npm install @prisma/adapter-pgPrisma 7 起 Client 由生成器生成到源码目录,需额外安装数据库驱动适配器(PostgreSQL 用
@prisma/adapter-pg,MySQL 用@prisma/adapter-mysql)。
初始化:
bash
npx prisma init生成文件:
text
prisma/
└── schema.prisma
.env.env:
env
DATABASE_URL="postgresql://user:password@localhost:5432/appdb"如果使用 MySQL:
env
DATABASE_URL="mysql://user:password@localhost:3306/appdb"不要把真实生产数据库密码提交到 Git。
7. schema.prisma 文件结构
基本结构:
prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
createdAt DateTime @default(now())
}三块内容:
datasource:告诉 Prisma 使用什么数据库。generator:告诉 Prisma 生成什么客户端。model:定义应用层数据模型。
8. datasource、generator、model
PostgreSQL datasource:
prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}MySQL datasource:
prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}SQLite datasource:
prisma
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}Model 示例:
prisma
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
createdAt DateTime @default(now())
}问号表示可空:
prisma
content String?对应数据库中允许为 NULL。
9. 第一次迁移
添加模型后执行:
bash
npx prisma migrate dev --name init它通常会做几件事:
- 根据
schema.prisma生成 SQL 迁移。 - 在数据库中执行迁移。
- 记录迁移历史。
- 重新生成 Prisma Client。
迁移文件大致在:
text
prisma/
└── migrations/
└── 20260625000000_init/
└── migration.sql迁移文件应该提交到 Git。不要只提交 schema.prisma,否则团队无法可靠复现数据库结构变化历史。
10. 生成 Prisma Client
手动生成:
bash
npx prisma generate使用:
ts
import { PrismaClient } from "./generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
const prisma = new PrismaClient({
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
});
const users = await prisma.user.findMany();在长期运行的服务中,PrismaClient 应复用,不要每个请求都 new 一个。
Node.js 服务示例:
ts
export const prisma = new PrismaClient({
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
});Next.js 开发模式下因为热更新可能重复创建实例,通常会做全局缓存。
第三篇:数据模型
11. 字段类型
常见 Prisma 类型:
prisma
model Example {
id Int @id @default(autoincrement())
uuid String @unique @default(uuid())
name String
age Int?
price Decimal
active Boolean @default(true)
metadata Json?
createdAt DateTime @default(now())
}常用对应关系:
| Prisma | PostgreSQL/MySQL 常见含义 |
|---|---|
String | text/varchar |
Int | integer |
BigInt | bigint |
Float | double precision/double |
Decimal | numeric/decimal |
Boolean | boolean/tinyint |
DateTime | timestamp/datetime |
Json | json/jsonb |
金额建议使用 Decimal,不要使用 Float。
12. 主键和唯一约束
自增主键:
prisma
model User {
id Int @id @default(autoincrement())
}UUID 主键:
prisma
model User {
id String @id @default(uuid())
}唯一约束:
prisma
model User {
id Int @id @default(autoincrement())
email String @unique
}复合唯一:
prisma
model Membership {
userId Int
teamId Int
@@unique([userId, teamId])
}复合唯一常用于防止重复关系,例如同一个用户不能重复加入同一个团队。
13. 默认值和更新时间
创建时间:
prisma
createdAt DateTime @default(now())更新时间:
prisma
updatedAt DateTime @updatedAt布尔默认:
prisma
published Boolean @default(false)注意:@updatedAt 是 Prisma 层帮你维护的字段。直接绕过 Prisma 用 SQL 更新数据时,要确认该字段是否还能按预期变化。
14. 枚举 enum
prisma
enum UserRole {
USER
ADMIN
}
model User {
id Int @id @default(autoincrement())
role UserRole @default(USER)
}适合枚举的场景:
- 用户角色
- 订单状态
- 支付状态
- 审核状态
枚举变更需要谨慎,尤其是生产数据库中已有数据时。删除枚举值前要确认没有数据还在使用该值。
15. 一对一、一对多、多对多
一对多:用户和文章。
prisma
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}查询用户及文章:
ts
const user = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true },
});一对一:用户和资料。
prisma
model User {
id Int @id @default(autoincrement())
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
bio String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
}多对多:文章和标签。
prisma
model Post {
id Int @id @default(autoincrement())
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}隐式多对多适合简单关系。如果中间表需要额外字段,例如创建时间、排序、创建人,就应显式建中间模型。
显式多对多:
prisma
model PostTag {
post Post @relation(fields: [postId], references: [id])
postId Int
tag Tag @relation(fields: [tagId], references: [id])
tagId Int
createdAt DateTime @default(now())
@@id([postId, tagId])
}16. 映射数据库表名和字段名
Prisma 推荐模型使用 PascalCase,字段使用 camelCase。但数据库中常用 snake_case。可以用 @map 和 @@map 映射。
prisma
model User {
id Int @id @default(autoincrement())
createdAt DateTime @map("created_at")
@@map("users")
}这样应用代码中使用:
ts
user.createdAt数据库中仍然是:
sql
users.created_at这在团队同时重视 TypeScript 命名和数据库命名规范时非常有用。
第四篇:Prisma Client 查询
17. 查询单条和多条
按唯一条件查:
ts
const user = await prisma.user.findUnique({
where: { email: "alice@example.com" },
});查第一条:
ts
const user = await prisma.user.findFirst({
where: { name: { contains: "Alice" } },
});查多条:
ts
const users = await prisma.user.findMany({
where: { active: true },
});区别:
findUnique要求where使用唯一字段。findFirst返回匹配条件的第一条。findMany返回数组。
18. 创建、更新、删除
创建:
ts
const user = await prisma.user.create({
data: {
email: "alice@example.com",
name: "Alice",
},
});更新:
ts
const user = await prisma.user.update({
where: { id: 1 },
data: { name: "Alice Chen" },
});批量更新:
ts
await prisma.user.updateMany({
where: { active: false },
data: { archived: true },
});删除:
ts
await prisma.user.delete({
where: { id: 1 },
});批量删除要谨慎:
ts
await prisma.log.deleteMany({
where: {
createdAt: {
lt: new Date("2025-01-01"),
},
},
});19. 条件过滤
常用过滤:
ts
const posts = await prisma.post.findMany({
where: {
published: true,
title: {
contains: "Prisma",
mode: "insensitive",
},
createdAt: {
gte: new Date("2026-01-01"),
},
},
});逻辑条件:
ts
const users = await prisma.user.findMany({
where: {
OR: [
{ email: { endsWith: "@example.com" } },
{ role: "ADMIN" },
],
},
});关系过滤:
ts
const users = await prisma.user.findMany({
where: {
posts: {
some: {
published: true,
},
},
},
});关系过滤常见关键词:
some:至少一个关联记录满足条件。every:所有关联记录满足条件。none:没有关联记录满足条件。
20. 排序、分页和游标
排序:
ts
const posts = await prisma.post.findMany({
orderBy: { createdAt: "desc" },
});offset 分页:
ts
const posts = await prisma.post.findMany({
skip: 40,
take: 20,
orderBy: { createdAt: "desc" },
});游标分页:
ts
const posts = await prisma.post.findMany({
cursor: { id: 1000 },
skip: 1,
take: 20,
orderBy: { id: "desc" },
});建议:
- 小数据后台列表可以使用
skip/take。 - 大数据、无限滚动、高频接口优先考虑 cursor。
- 分页必须有稳定排序字段。
21. include 与 select
include 加载关联数据:
ts
const posts = await prisma.post.findMany({
include: {
author: true,
tags: true,
},
});select 选择字段:
ts
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
name: true,
},
});不要在接口中无脑返回所有字段。例如密码 hash、内部备注、审计字段都不应该随便暴露。
可以组合:
ts
const posts = await prisma.post.findMany({
select: {
id: true,
title: true,
author: {
select: {
id: true,
name: true,
},
},
},
});22. 聚合、分组和计数
计数:
ts
const count = await prisma.user.count({
where: { active: true },
});聚合:
ts
const result = await prisma.order.aggregate({
_sum: { amount: true },
_avg: { amount: true },
});分组:
ts
const rows = await prisma.order.groupBy({
by: ["status"],
_count: { _all: true },
_sum: { amount: true },
});复杂报表不一定适合强行用 Prisma Client 表达。如果 SQL 更清晰,可以使用原生 SQL。
第五篇:迁移、事务与生产实践
23. Prisma Migrate 工作流
开发环境:
bash
npx prisma migrate dev --name add_user_table生产环境:
bash
npx prisma migrate deploy二者区别:
migrate dev面向开发,会生成迁移,也可能提示重置开发库。migrate deploy面向生产,只执行已经存在的迁移文件。
生产环境不要运行 migrate dev。
团队流程:
- 修改
schema.prisma。 - 本地运行
migrate dev。 - 检查生成的
migration.sql。 - 提交 schema 和 migrations。
- CI/CD 或发布流程中运行
migrate deploy。
24. 开发迁移和生产迁移
危险变更:
- 删除字段
- 改字段类型
- 添加非空字段但没有默认值
- 重命名字段
- 大表添加索引
- 修改枚举值
安全做法通常是分阶段:
- 新增字段,允许为空。
- 应用双写或回填数据。
- 代码切到新字段。
- 确认无旧字段依赖。
- 再删除旧字段。
Prisma 迁移很方便,但不能代替上线策略。数据库迁移的风险来自真实数据量、锁、索引构建、历史数据和兼容性。
25. 事务
数组事务:
ts
await prisma.$transaction([
prisma.account.update({
where: { id: 1 },
data: { balance: { decrement: 100 } },
}),
prisma.account.update({
where: { id: 2 },
data: { balance: { increment: 100 } },
}),
]);交互式事务:
ts
await prisma.$transaction(async (tx) => {
const from = await tx.account.findUnique({
where: { id: 1 },
});
if (!from || from.balance < 100) {
throw new Error("余额不足");
}
await tx.account.update({
where: { id: 1 },
data: { balance: { decrement: 100 } },
});
await tx.account.update({
where: { id: 2 },
data: { balance: { increment: 100 } },
});
});事务中不要做慢操作:
- 不要发 HTTP 请求。
- 不要等待用户输入。
- 不要做耗时文件处理。
事务应该短、明确、只包住必须保持一致的数据库操作。
26. 原生 SQL
Prisma 支持原生 SQL。适合:
- 复杂报表
- 特定数据库函数
- 批量更新
- 性能关键查询
- Prisma Client 难表达的 SQL
查询:
ts
const rows = await prisma.$queryRaw`
select id, email
from users
where email like ${"%@example.com"}
`;执行:
ts
await prisma.$executeRaw`
update users
set active = false
where last_login_at < ${cutoff}
`;不要拼接不可信字符串:
ts
// 危险示意,不要这样做
await prisma.$queryRawUnsafe(`select * from users where email = '${email}'`);原生 SQL 能力很重要。会 Prisma 不代表可以不学 SQL。
27. 性能优化
常见问题:
- 返回字段太多。
- include 关联过深。
- N+1 查询。
- 缺少数据库索引。
- 分页方式不适合大数据。
- 事务时间太长。
- 连接数配置不合理。
优化建议:
- 用
select限制字段。 - 谨慎使用深层
include。 - 为 where/orderBy/join 相关字段建立合适索引。
- 高频接口记录慢查询日志。
- 复杂查询用
EXPLAIN分析数据库执行计划。 - Serverless 场景注意连接池。
Prisma 能提高开发效率,但性能瓶颈通常仍然出在数据库查询本身。
28. 与 PostgreSQL/MySQL 的关系
Prisma Schema:
prisma
model User {
id Int @id @default(autoincrement())
email String @unique
}最终会映射成数据库表:
sql
create table users (
id integer primary key generated by default as identity,
email text unique not null
);不同数据库的差异会影响 Prisma:
- PostgreSQL 的
Json通常映射到 JSON/JSONB 能力。 - MySQL 的字符串、时间、索引长度、字符集规则有自己的特点。
- 不同数据库支持的字段类型和默认值能力不同。
- 事务隔离、锁、全文搜索、数组类型等能力也不同。
所以 Prisma 项目仍然要明确底层数据库是什么。不要以为换 datasource provider 就一定可以无痛迁移。
29. 常见误区和学习路线
误区一:用了 Prisma 就不用学 SQL。
纠正:Prisma 负责提高应用层开发效率,SQL 和数据库原理仍然是基础。
误区二:schema.prisma 就是数据库真实结构。
纠正:schema 是模型描述,真实结构要看数据库和 migration.sql。
误区三:迁移文件不用看。
纠正:生产前必须审查 SQL 迁移,尤其是删除字段、改类型、建索引。
误区四:include 越方便越应该多用。
纠正:关联加载要关注返回体积和查询成本。
学习路线:
- 学会 PostgreSQL 或 MySQL 基础 SQL。
- 理解表关系、主键、外键、索引、事务。
- 学会
schema.prisma建模。 - 掌握 Prisma Client CRUD。
- 掌握关系查询和事务。
- 学会 Prisma Migrate 开发/生产流程。
- 能写 raw SQL 并分析执行计划。
- 在真实项目中处理迁移、连接池和性能问题。
实战练习项目
项目一:博客系统
- User
- Post
- Comment
- Tag
- PostTag
练习:
- 一对多
- 显式多对多
- 分页查询
- include/select
- Prisma Migrate
项目二:订单系统
- User
- Product
- Order
- OrderItem
- Payment
练习:
- Decimal 金额
- 事务
- 库存扣减
- 状态枚举
- 原生 SQL 报表
项目三:Monorepo 全栈系统
text
apps/web
apps/api
packages/database
packages/ui练习:
- 把 Prisma schema 放在
packages/database。 - API 复用同一个 Prisma Client。
- Web 复用数据库类型。
- 使用 Turbo 编排 generate/build/typecheck。
快速记忆清单
- Prisma 是 ORM,不是数据库。
schema.prisma是模型中心,迁移文件才是真实结构变更历史。Prisma Client是根据 schema 生成的类型安全查询客户端。- 开发用
migrate dev,生产用migrate deploy。 - 金额用
Decimal,时间字段要统一规范。 - 关系查询方便,但要警惕过深 include 和 N+1。
- 复杂查询、报表和性能关键路径可以使用 raw SQL。
- 真正的性能优化离不开数据库索引、事务和执行计划。