Prisma — 类型安全 ORM
是什么
Prisma 是一套用一份 schema 文件描述数据库表,自动生成 TypeScript 类型 + Query 客户端的 ORM。
日常类比:写菜单(schema.prisma)→ 系统自动配出每道菜的下单按钮(type-safe API)。你不手写 SQL、不手写 TypeScript 类型——一份 schema 喂进去,编译期就能知道 query 写错没。
model User { id Int @id @default(autoincrement()) email String @unique posts Post[]}跑 prisma generate 后:
const user = await prisma.user.findUnique({ where: { email: 'a@b.com' }, include: { posts: true },})// user 类型自动推为:(User & { posts: Post[] }) | null为什么重要
不理解 Prisma 解决的问题,下面这些事都没法解释:
- 为什么 TypeScript ORM 圈把 Prisma / drizzle / kysely 并称”三足鼎立”
- 为什么编辑器能自动补全
prisma.user.findMany({ where: { ... } })里所有合法字段 - 为什么
prisma migrate dev能读懂你 schema 改了什么、自动生成 SQL 迁移文件 - 为什么 Prisma Studio 那种”GUI 直接看 / 改数据”成为团队 onboarding 标配
ORM 历史上有过 Active Record 派(Rails)/ Data Mapper 派(Hibernate)/ query builder 派(Knex)。Prisma 是第一个把”schema 当源头 + codegen 当桥梁 + 类型系统当护栏”三件事拧到一起的方案——这是它的标杆地位来源。
核心要点
Prisma 的运转可以拆成 三块:
-
声明式
schema.prisma(DSL):用model User { ... }写表结构。@id/@unique/@default(now())是字段级修饰符,@@index([email])是 model 级。整个文件纯声明,没有if/else/ 函数定义——这让它能被工具反向解析、双向消费。 -
生成的 Prisma Client(强类型 + auto-complete):跑
prisma generate后,node_modules/.prisma/client/出一份index.d.ts——把每个 model 编译成一组 TypeScript 类型(User/UserWhereInput/UserCreateInput/ …)。import { PrismaClient } from '@prisma/client'拿到的就是这套强类型 API。 -
Migrations(开发和部署都自动):
prisma migrate dev比对”当前 schema vs 上次迁移后的状态”,自动生成 SQL 迁移文件并应用;prisma migrate deploy在生产环境只跑、不生成。两条命令分开是为了避免生产环境意外建表。
实践案例
案例 1:定义 schema
datasource db { provider = "postgresql" url = env("DATABASE_URL")}
generator client { provider = "prisma-client-js"}
model User { id Int @id @default(autoincrement()) email String @unique posts Post[]}
model Post { id Int @id @default(autoincrement()) title String authorId Int author User @relation(fields: [authorId], references: [id])}注意几件事:
User.posts: Post[]和Post.author必须双向定义,否则 schema 校验不过@relation(fields: [authorId], references: [id])显式声明”authorId 是外键,指向 User.id”?表可空,[]表数组——借鉴 GraphQL SDL 语法精神,不是 SQL 习惯
案例 2:query 用 include 嵌套读
import { PrismaClient } from '@prisma/client'const prisma = new PrismaClient()
const user = await prisma.user.findUnique({ where: { email: 'a@b.com' }, include: { posts: true },})// user 类型自动推为:(User & { posts: Post[] }) | nullinclude 是 Prisma 招牌——你不写 JOIN,关系数据就拿出来了。引擎背后把 include 编译成多条 batch query 在 client 侧组装(避免笛卡尔积膨胀)。
案例 3:改 schema 跑 migration
# 编辑 schema.prisma:给 User 加 bio String?# 然后一行命令:npx prisma migrate dev --name add_bioPrisma 自动:
- 比对当前 schema 和
prisma/migrations/下已有迁移的累计状态 - 算出 diff:
ALTER TABLE "User" ADD COLUMN "bio" TEXT - 写到
prisma/migrations/<timestamp>_add_bio/migration.sql - 在真实 db 上跑这条 SQL
- 在 db 的
_prisma_migrations表里记录已应用版本
部署到生产时,CI 跑 prisma migrate deploy——只读迁移文件夹、按顺序应用,不生成新文件。
踩过的坑
-
大型 query 生成的 SQL 不一定最优:
include嵌套三层({ posts: { include: { tags: { include: ... } } } })会拆成多条 round trip。引擎把同层 batch 成一次(避免 N+1),但 round trip 总数随嵌套深度线性增长。复杂场景必须打开log: ['query']看真实 SQL。 -
Edge Runtime(Cloudflare Workers / Vercel Edge)支持有限:默认 query engine 是 Rust binary,bundle 几 MB——塞不进 Workers 的 100KB 限制。要上 Edge 必须搭 Prisma Accelerate(把 query 转 HTTP 走云端 connection pool)或用 v5+ 的 wasm engine(功能滞后中)。
-
Schema 改动后忘记
prisma generate:Prisma Client 是 codegen 出来的——schema 改了不重 generate,IDE 看到的还是旧类型,runtime 也是旧 schema 副本,“修改没生效”。习惯做法:把prisma generate挂在postinstall钩子上,CI 跑npm install时自动同步。 -
Connection pool 与 serverless 不友好:每次 lambda / Workers 冷启动都新建一份 PrismaClient = 新建一组 db connection,db 的
max_connections几百很快被打爆。解决方案:① PgBouncer 中间件 ② Prisma Accelerate 云端共享池 ③ 数据库选 Neon / Planetscale 这种自带 pooling 的 serverless db。
适用 vs 不适用场景
适用:
- prototype / 中后台 / 内部工具——DX 极好,Studio GUI 直接当后台用
- 团队不熟 SQL,想要”写 TypeScript 就能查 db”的体感
- 跨多种数据库(PG / MySQL / SQLite / SQL Server / MongoDB / CockroachDB)—— Prisma 一份代码六种数据库
- 需要 schema migration 自动化(不想手写 SQL diff)
不适用:
- 边缘运行时 + 在意 bundle / cold start → 选 drizzle / kysely
- 极度复杂的 SQL(CTE / window function /
LATERAL JOIN)→ 必须$queryRaw,失去类型安全 - 需要”看见每条 SQL”做性能 tuning → 选 kysely 这种 query builder
- 多 datasource transaction 跨库写一致 → Prisma 不支持跨 datasource transaction
历史小故事(可跳过)
- 2016 年:Graphcool 创业(柏林),做 GraphQL backend-as-a-service。
- 2019 年:Graphcool 关停,团队转做 Prisma 1(GraphQL-first 的 ORM 雏形)。
- 2020 年:完全重写为 Prisma 2——砍掉 GraphQL 中间层,DSL + codegen + Rust query engine 这套架构定型。
- 2022-2024 年:Prisma 5.x 主线迭代,wasm engine 进 preview。
- 2026 年:v6 推 wasm engine 进主流,补 Edge runtime 这块债。
学到什么
- schema 当源头——这个判断比”entity class 当源头”或”SQL 当源头”更适合 TypeScript 时代。codegen 是把”schema 真理”投射到”TypeScript 类型”的桥。
- DSL 派 vs schema-as-code 派——Prisma 选 DSL(
schema.prisma单文件),Drizzle 选 schema-as-code(TS 对象散落)。两条路都活着,证明各有市场——审美 + 工程权衡之争,不是技术对错。 - Rust engine + binary protocol 的代价——2019 年合理(TS 类型系统能力不够),2026 年是 Edge runtime 的负担。任何长期项目都会遇到这种”早期对的判断在 5 年后成为束缚”的路径依赖。
include隐藏 SQL 的代价——happy path 极优雅,但 debug 难——必须打开 query log 才知道一行代码跑了几条 SQL。“藏 SQL”和”露 SQL”是两种世界观。
延伸阅读
- 官方文档:prisma.io/docs(中文文档完整,30 分钟跑起来)
- 源码:prisma/prisma(client + cli + migrate + generator-helper)
- 引擎源码:prisma/prisma-engines(Rust workspace,query / migrate / introspection 三个 engine)
- drizzle —— schema-as-code 派的旗手,Prisma 的对照组
- kysely —— 纯 SQL builder 派,不管 schema 让你写 type-safe SQL
关联
- drizzle —— TypeScript ORM 三足之一,schema 写在 TS 对象里、看得见 SQL、Edge runtime 友好
- kysely —— TypeScript ORM 三足之一,纯 query builder,1:1 映射 SQL
- graphql —— Prisma 的 schema DSL 受 GraphQL SDL 影响,
?/[]语法精神来自 SDL - postgres —— Prisma 在 PG 上功能最完整,Edge 时代的搭档
反向链接
- auth-js —— Auth.js — 让 OAuth 登录和会话存储变成两个抽象
- better-auth —— better-auth — 把登录/OAuth/2FA/Passkey 拼成一行配置的 TS 认证框架
- cal-com —— cal.com — 自己能托管的开源 Calendly
- drizzle —— Drizzle ORM — 轻量 SQL-like ORM
- edgedb —— EdgeDB / Gel — 在 Postgres 上长出图风查询语言,让类型系统替你做 ORM
- gqlgen —— gqlgen — Go 用 schema 先写好再让编译器生成 GraphQL server
- kysely —— Kysely — TypeScript SQL 查询构建器
- mikro-orm —— MikroORM — Data Mapper Identity Map ORM
- nestjs —— NestJS — 把 Angular 思想搬到 Node.js 后端的企业级框架
- next-js —— Next.js — React 全栈框架
- postgres-js —— postgres.js — 写 SQL 但语法层就防注入的 Node 客户端
- sequelize —— Sequelize — 老牌 Node ORM
- typeorm —— TypeORM — Decorator-based ORM
- unstorage —— unstorage — 让 KV 存储不绑死运行时的统一抽象层