Skip to content

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 文档。

官方资料:


目录 ​

第一篇:基础认知 ​

  1. Prisma ORM 是什么
  2. ORM 解决什么问题
  3. Prisma 和传统 ORM 的区别
  4. Prisma 的核心组成
  5. Prisma 适合什么项目

第二篇:快速入门 ​

  1. 安装与初始化
  2. schema.prisma 文件结构
  3. datasource、generator、model
  4. 第一次迁移
  5. 生成 Prisma Client

第三篇:数据模型 ​

  1. 字段类型
  2. 主键和唯一约束
  3. 默认值和更新时间
  4. 枚举 enum
  5. 一对一、一对多、多对多
  6. 映射数据库表名和字段名

第四篇:Prisma Client 查询 ​

  1. 查询单条和多条
  2. 创建、更新、删除
  3. 条件过滤
  4. 排序、分页和游标
  5. include 与 select
  6. 聚合、分组和计数

第五篇:迁移、事务与生产实践 ​

  1. Prisma Migrate 工作流
  2. 开发迁移和生产迁移
  3. 事务
  4. 原生 SQL
  5. 性能优化
  6. 与 PostgreSQL/MySQL 的关系
  7. 常见误区和学习路线

第一篇:基础认知 ​

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 deploy

5. 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-pg

Prisma 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

它通常会做几件事:

  1. 根据 schema.prisma 生成 SQL 迁移。
  2. 在数据库中执行迁移。
  3. 记录迁移历史。
  4. 重新生成 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())
}

常用对应关系:

PrismaPostgreSQL/MySQL 常见含义
Stringtext/varchar
Intinteger
BigIntbigint
Floatdouble precision/double
Decimalnumeric/decimal
Booleanboolean/tinyint
DateTimetimestamp/datetime
Jsonjson/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。

团队流程:

  1. 修改 schema.prisma。
  2. 本地运行 migrate dev。
  3. 检查生成的 migration.sql。
  4. 提交 schema 和 migrations。
  5. CI/CD 或发布流程中运行 migrate deploy。

24. 开发迁移和生产迁移 ​

危险变更:

  • 删除字段
  • 改字段类型
  • 添加非空字段但没有默认值
  • 重命名字段
  • 大表添加索引
  • 修改枚举值

安全做法通常是分阶段:

  1. 新增字段,允许为空。
  2. 应用双写或回填数据。
  3. 代码切到新字段。
  4. 确认无旧字段依赖。
  5. 再删除旧字段。

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. 性能优化 ​

常见问题:

  1. 返回字段太多。
  2. include 关联过深。
  3. N+1 查询。
  4. 缺少数据库索引。
  5. 分页方式不适合大数据。
  6. 事务时间太长。
  7. 连接数配置不合理。

优化建议:

  • 用 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 越方便越应该多用。

纠正:关联加载要关注返回体积和查询成本。

学习路线:

  1. 学会 PostgreSQL 或 MySQL 基础 SQL。
  2. 理解表关系、主键、外键、索引、事务。
  3. 学会 schema.prisma 建模。
  4. 掌握 Prisma Client CRUD。
  5. 掌握关系查询和事务。
  6. 学会 Prisma Migrate 开发/生产流程。
  7. 能写 raw SQL 并分析执行计划。
  8. 在真实项目中处理迁移、连接池和性能问题。

实战练习项目 ​

项目一:博客系统

  • 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。
  • 真正的性能优化离不开数据库索引、事务和执行计划。

基于 VitePress 构建的个人学习文档知识库