Prisma 是当前 Node.js/TypeScript 生态中最现代化的 ORM,它以声明式 Schema 管理数据库结构,以类型安全的方式生成查询客户端。与 PostgreSQL 结合,Prisma 可以在保持 SQL 全部能力的同时,获得完整的 TypeScript 类型推导和智能补全。
一句话总结:Prisma 让你写 TypeScript 代码时享受 IDE 自动补全的 SQL。Schema 是数据库结构,Migration 是版本控制,Client 是类型安全的查询器。
一、Schema 设计
1.1 完整 Schema 示例
// schema.prisma
// 生成器:生成 TypeScript 客户端
generator client {
provider = "prisma-client-js"
}
// 数据源:PostgreSQL 连接
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// 用户模型
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users")
@@index([email])
@@index([createdAt])
}
enum Role {
USER
ADMIN
MODERATOR
}
// 文章模型
model Post {
id Int @id @default(autoincrement())
title String
slug String @unique
content String?
published Boolean @default(false)
views Int @default(0)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int @map("author_id")
tags Tag[]
metadata Json? // PostgreSQL JSONB 列
createdAt DateTime @default(now()) @map("created_at")
@@map("posts")
@@index([published, createdAt])
@@index([slug])
}
// 标签模型
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
@@map("tags")
}
// 用户资料(一对一关系)
model Profile {
id Int @id @default(autoincrement())
bio String?
avatar String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int @unique @map("user_id")
@@map("profiles")
}
1.2 Schema 设计原则
| 原则 | 说明 |
|---|---|
使用 @@map 避免驼峰到蛇形转换问题 | 明确指定数据库列/表名 |
为 DateTime 加 @default(now()) | 自动填充创建时间 |
为频繁查询字段加 @@index | Schema 级别声明索引 |
关系字段必须加 onDelete: Cascade 或 Restrict | 明确级联行为,避免孤儿数据 |
JSONB 字段用 Json 类型 | Prisma 自动映射为 PostgreSQL JSONB |
| 枚举类型优先于 String | 类型安全 + 数据一致性 |
二、Migration 工作流
2.1 初始化与迭代
# 初始化 Schema 并生成第一个 Migration
npx prisma migrate dev --name init
# Model 变更后生成新 Migration
npx prisma migrate dev --name add_user_role
# 部署到生产环境
npx prisma migrate deploy
# 生成客户端(类型安全)
npx prisma generate
# 可视化数据库
npx prisma studio
2.2 生产环境迁移策略
# CI/CD 步骤(Docker 容器内)
npx prisma generate # 先编译出 Prisma Client
dotenv -e .env.production npx prisma migrate deploy # 只 apply 待执行迁移
npm run build # 构建应用
npm start # 启动
注意:生产环境只用 prisma migrate deploy,绝不用 prisma migrate dev(它会自动提示创建迁移,不适合无人值守的 CI)。
2.3 版本控制约定
prisma/
├── dev.db # SQLite(测试用,可选)
├── migrations/
│ ├── 20251120000000_init/
│ │ └── migration.sql
│ └── 20251121000000_add_user_role/
│ └── migration.sql
├── schema.prisma
├── seed.ts
└── lib/
└── prisma.ts # 单例 Prisma Client
三、Seed 数据:初始化测试数据
3.1 Seed 脚本
// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// 创建种子数据
const alice = await prisma.user.upsert({
where: { email: 'alice@example.com' },
update: {},
create: {
email: 'alice@example.com',
name: 'Alice',
role: 'USER',
posts: {
create: [
{
title: 'Getting Started with Prisma',
slug: 'getting-started-prisma',
published: true,
tags: { create: [{ name: 'prisma' }, { name: 'database' }] },
},
],
},
profile: { create: { bio: 'Full-stack developer' } },
},
});
console.log({ alice });
}
main()
.catch((e) => { console.error(e); process.exit(1); })
.finally(async () => await prisma.$disconnect());
3.2 配置 Seed 命令
// package.json
{
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"scripts": {
"db:seed": "prisma db seed"
}
}
执行:npx prisma db seed 会在 migrate reset(清空数据库重新创建)时自动运行。
四、类型安全查询
4.1 CRUD 完整示例
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'], // 开发时开启
});
// Create(含关联嵌套创建)
const user = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
role: 'ADMIN',
profile: { create: { bio: 'Administrator' } },
},
include: { profile: true }, // 返回关联数据
});
// Read(条件 + 关联 + 分页)
const posts = await prisma.post.findMany({
where: {
published: true,
author: { email: { contains: '@example.com' } },
},
include: { author: { select: { name: true, email: true } }, tags: true },
orderBy: [{ createdAt: 'desc' }],
take: 10,
skip: 0, // offset for pagination
});
// Update(计数器原子性)
const updated = await prisma.post.update({
where: { id: 1 },
data: { views: { increment: 1 } }, // 原子自增
});
// Delete(级联删除由 Schema 中 onDelete: Cascade 控制)
await prisma.user.delete({ where: { id: 1 } });
4.2 复杂查询模式
// 1. Filter 多条件组合
const users = await prisma.user.findMany({
where: {
AND: [
{ role: 'USER' },
{ createdAt: { gte: new Date('2025-01-01') } },
{ posts: { some: { published: true } } }, // 至少有一篇已发布文章
],
},
});
// 2. Aggregation(聚合)
const stats = await prisma.post.groupBy({
by: ['published'],
_count: { id: true },
_avg: { views: true },
_sum: { views: true },
_max: { createdAt: true },
});
// 3. DISTINCT 查询
const distinctTags = await prisma.post.findMany({
distinct: ['authorId'],
});
// 4. SELECT 指定字段(减少返回数据量)
const slimPosts = await prisma.post.findMany({
select: { id: true, title: true, slug: true },
take: 5,
});
4.3 事务处理
// 方式 1:交互式事务(推荐大多数场景)
const [user, post] = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { email: 'charlie@example.com', name: 'Charlie' },
});
const post = await tx.post.create({
data: {
title: "Charlie's Post",
slug: 'charlies-post',
authorId: user.id,
},
});
return [user, post];
});
// 方式 2:批量事务(串行执行,一步失败全回滚)
const result = await prisma.$transaction([
prisma.user.update({ where: { id: 1 }, data: { name: 'Updated' } }),
prisma.post.update({ where: { id: 1 }, data: { views: { increment: 1 } } }),
]);
// 方式 3:事务选项(隔离级别 + 重试)
await prisma.$transaction(
async (tx) => {
const user = await tx.user.create({ data: { email: 'dave@example.com' } });
return user;
},
{
isolationLevel: 'Serializable',
maxWait: 5000,
timeout: 10000,
}
);
五、Raw SQL 查询
当 Prisma 的 API 不能满足复杂查询时,Raw SQL 仍然可用:
// 简单 Raw Query(返回动态类型,不安全)
const result = await prisma.$queryRaw`
SELECT * FROM posts WHERE published = true ORDER BY views DESC LIMIT 5
`;
// 参数化 Raw Query(防 SQL 注入)
const email = 'alice@example.com';
const user = await prisma.$queryRaw`
SELECT * FROM users WHERE email = ${email}
`;
// Prisma 类型安全的 Raw Query(typed-sql 功能)
// 先运行 prisma db generate 生成类型,然后:
import { getActiveUsers } from '@prisma/client/sql';
const users = await prisma.$queryRawTyped(getActiveUsers());
// Raw Execute(INSERT/UPDATE/DELETE)
await prisma.$executeRaw`
INSERT INTO tags (name) VALUES ('new-tag') ON CONFLICT DO NOTHING
`;
六、Middleware 与生命周期钩子
6.1 Middleware 拦截查询
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// 全局 Middleware:记录慢查询
prisma.$use(async (params, next) => {
const start = performance.now();
const result = await next(params);
const end = performance.now();
const duration = end - start;
if (duration > 1000) {
console.warn(`[SLOW QUERY] ${params.model}.${params.action} took ${duration.toFixed(2)}ms`);
}
return result;
});
// Middleware 2:自动注入用户上下文
prisma.$use(async (params, next) => {
if (params.model === 'Post' && params.action === 'create') {
params.args.data.slug = params.args.data.slug ||
params.args.data.title.toLowerCase().replace(/\s+/g, '-');
}
return next(params);
});
export default prisma;
6.2 使用单例
// 避免 Next.js 开发热重载产生多个连接
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
七、连接池配置
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
});
// 连接池池大小需在连接字符串中配置
// DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=20&pool_timeout=5
// production 推荐 via PgBouncer:`DATABASE_URL=postgresql://user:pass@pgbouncer:6432/db?pgbouncer=true`
连接池最佳实践:别在 Prisma 层设太大,connection_limit=10 左右;后端数据库层用 PgBouncer 处理高并发小连接。Prisma 层 = 应用短连接,PgBouncer = 汇聚层,PostgreSQL = 长连接。
常见问题(FAQ)
Prisma 的 @db.Json 和 @db.JsonB 有什么区别?
在 PostgreSQL 中 Prisma Json 类型默认映射到 JSONB。如果你想用纯文本 JSON(性能更差、无索引),可在 Schema 中写 @db.Json(注意 Prisma 5+ 已有变化)。
Migration 冲突怎么办(多个开发者同时创建)?
- 先执行
prisma migrate resolve解决已应用的重复 Migration - 将冲突的
migration.sql合并为一个 - 用
prisma migrate dev --create-only新建而不自动应用
Seed 脚本在 CI 中如何运行?
npx prisma migrate reset --force # 清空数据库 + 应用全部迁移 + 运行 seed
# CI 中 --force 跳过确认提示
Prisma 不支持的数据库特性怎么用 Raw SQL?
CTE(WITH 语句)、窗口函数、自定义函数 — 这些 Prisma 4/5 的 Client API 仍不支持,通过 $queryRaw 使用是标准做法。
相关阅读
- PostgreSQL 详解 — 核心架构、MVCC、JSONB
- Next.js + Postgres SaaS 实战 — 全栈部署
- Node.js + Prisma + PostgreSQL 实战 — Nest.js/Express 集成
- PostgreSQL 性能优化 — EXPLAIN、索引、调参
- PostgreSQL Docker 部署 — 容器化
- PostgreSQL 专题导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。