跳转到内容

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 的运转可以拆成 三块

  1. 声明式 schema.prisma(DSL):用 model User { ... } 写表结构。@id / @unique / @default(now()) 是字段级修饰符,@@index([email]) 是 model 级。整个文件纯声明,没有 if / else / 函数定义——这让它能被工具反向解析、双向消费。

  2. 生成的 Prisma Client(强类型 + auto-complete):跑 prisma generate 后,node_modules/.prisma/client/ 出一份 index.d.ts——把每个 model 编译成一组 TypeScript 类型(User / UserWhereInput / UserCreateInput / …)。import { PrismaClient } from '@prisma/client' 拿到的就是这套强类型 API。

  3. 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[] }) | null

include 是 Prisma 招牌——你不写 JOIN,关系数据就拿出来了。引擎背后把 include 编译成多条 batch query 在 client 侧组装(避免笛卡尔积膨胀)。

案例 3:改 schema 跑 migration

Terminal window
# 编辑 schema.prisma:给 User 加 bio String?
# 然后一行命令:
npx prisma migrate dev --name add_bio

Prisma 自动:

  1. 比对当前 schema 和 prisma/migrations/ 下已有迁移的累计状态
  2. 算出 diff:ALTER TABLE "User" ADD COLUMN "bio" TEXT
  3. 写到 prisma/migrations/<timestamp>_add_bio/migration.sql
  4. 在真实 db 上跑这条 SQL
  5. 在 db 的 _prisma_migrations 表里记录已应用版本

部署到生产时,CI 跑 prisma migrate deploy——只读迁移文件夹、按顺序应用,不生成新文件。

踩过的坑

  1. 大型 query 生成的 SQL 不一定最优include 嵌套三层({ posts: { include: { tags: { include: ... } } } })会拆成多条 round trip。引擎把同层 batch 成一次(避免 N+1),但 round trip 总数随嵌套深度线性增长。复杂场景必须打开 log: ['query'] 看真实 SQL。

  2. Edge Runtime(Cloudflare Workers / Vercel Edge)支持有限:默认 query engine 是 Rust binary,bundle 几 MB——塞不进 Workers 的 100KB 限制。要上 Edge 必须搭 Prisma Accelerate(把 query 转 HTTP 走云端 connection pool)或用 v5+ 的 wasm engine(功能滞后中)。

  3. Schema 改动后忘记 prisma generate:Prisma Client 是 codegen 出来的——schema 改了不重 generate,IDE 看到的还是旧类型,runtime 也是旧 schema 副本,“修改没生效”。习惯做法:把 prisma generate 挂在 postinstall 钩子上,CI 跑 npm install 时自动同步。

  4. 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 这块债。

学到什么

  1. schema 当源头——这个判断比”entity class 当源头”或”SQL 当源头”更适合 TypeScript 时代。codegen 是把”schema 真理”投射到”TypeScript 类型”的桥。
  2. DSL 派 vs schema-as-code 派——Prisma 选 DSL(schema.prisma 单文件),Drizzle 选 schema-as-code(TS 对象散落)。两条路都活着,证明各有市场——审美 + 工程权衡之争,不是技术对错。
  3. Rust engine + binary protocol 的代价——2019 年合理(TS 类型系统能力不够),2026 年是 Edge runtime 的负担。任何长期项目都会遇到这种”早期对的判断在 5 年后成为束缚”的路径依赖。
  4. 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 存储不绑死运行时的统一抽象层